From 0b628c85a57f28ce22b2c6ab355be726fd6c6e65 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 4 Dec 2024 10:36:41 +0400 Subject: [PATCH 001/175] updated create community dialog to mange edit/create --- .../widgets/blank_community_widget.dart | 2 + .../community_structure_header_widget.dart | 84 ++++++++++++------- .../widgets/community_structure_widget.dart | 2 + .../dialogs/create_community_dialog.dart | 35 ++++++-- 4 files changed, 88 insertions(+), 35 deletions(-) diff --git a/lib/pages/spaces_management/widgets/blank_community_widget.dart b/lib/pages/spaces_management/widgets/blank_community_widget.dart index 6b97c956..a57f3c18 100644 --- a/lib/pages/spaces_management/widgets/blank_community_widget.dart +++ b/lib/pages/spaces_management/widgets/blank_community_widget.dart @@ -72,6 +72,7 @@ class _BlankCommunityWidgetState extends State { showDialog( context: parentContext, builder: (context) => CreateCommunityDialog( + isEditMode: false, communities: widget.communities, onCreateCommunity: (String communityName, String description) { parentContext.read().add( @@ -84,4 +85,5 @@ class _BlankCommunityWidgetState extends State { ), ); } + } diff --git a/lib/pages/spaces_management/widgets/community_structure_header_widget.dart b/lib/pages/spaces_management/widgets/community_structure_header_widget.dart index a940ecf6..72d6228e 100644 --- a/lib/pages/spaces_management/widgets/community_structure_header_widget.dart +++ b/lib/pages/spaces_management/widgets/community_structure_header_widget.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; +import 'package:syncrow_web/pages/spaces_management/widgets/dialogs/create_community_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; -class CommunityStructureHeader extends StatelessWidget { +class CommunityStructureHeader extends StatefulWidget { final String? communityName; final bool isEditingName; final bool isSave; @@ -13,19 +15,28 @@ class CommunityStructureHeader extends StatelessWidget { final VoidCallback onDelete; final VoidCallback onEditName; final ValueChanged onNameSubmitted; + final List communities; + final CommunityModel? community; - const CommunityStructureHeader({ - Key? key, - required this.communityName, - required this.isSave, - required this.isEditingName, - required this.nameController, - required this.onSave, - required this.onDelete, - required this.onEditName, - required this.onNameSubmitted, - }) : super(key: key); + const CommunityStructureHeader( + {super.key, + required this.communityName, + required this.isSave, + required this.isEditingName, + required this.nameController, + required this.onSave, + required this.onDelete, + required this.onEditName, + required this.onNameSubmitted, + this.community, + required this.communities}); + @override + State createState() => + _CommunityStructureHeaderState(); +} + +class _CommunityStructureHeaderState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -60,47 +71,62 @@ class CommunityStructureHeader extends StatelessWidget { ); } + void _showCreateCommunityDialog(BuildContext parentContext) { + showDialog( + context: parentContext, + builder: (context) => CreateCommunityDialog( + isEditMode: true, + communities: widget.communities, + communityToEdit: widget.community, + onCreateCommunity: (String communityName, String description) { + widget.onNameSubmitted(communityName); + }, + ), + ); + } + Widget _buildCommunityInfo(ThemeData theme, double screenWidth) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Community Structure', - style: theme.textTheme.headlineLarge?.copyWith(color: ColorsManager.blackColor), + style: theme.textTheme.headlineLarge + ?.copyWith(color: ColorsManager.blackColor), ), - if (communityName != null) + if (widget.communityName != null) Row( children: [ Expanded( child: Row( children: [ - if (!isEditingName) + if (!widget.isEditingName) Flexible( child: Text( - communityName!, - style: - theme.textTheme.bodyLarge?.copyWith(color: ColorsManager.blackColor), + widget.communityName!, + style: theme.textTheme.bodyLarge + ?.copyWith(color: ColorsManager.blackColor), overflow: TextOverflow.ellipsis, maxLines: 1, ), ), - if (isEditingName) + if (widget.isEditingName) SizedBox( width: screenWidth * 0.1, child: TextField( - controller: nameController, + controller: widget.nameController, decoration: const InputDecoration( border: InputBorder.none, isDense: true, ), - style: - theme.textTheme.bodyLarge?.copyWith(color: ColorsManager.blackColor), - onSubmitted: onNameSubmitted, + style: theme.textTheme.bodyLarge + ?.copyWith(color: ColorsManager.blackColor), + onSubmitted: widget.onNameSubmitted, ), ), const SizedBox(width: 2), GestureDetector( - onTap: onEditName, + onTap: () => _showCreateCommunityDialog(context), child: SvgPicture.asset( Assets.iconEdit, width: 16, @@ -110,7 +136,7 @@ class CommunityStructureHeader extends StatelessWidget { ], ), ), - if (isSave) ...[ + if (widget.isSave) ...[ const SizedBox(width: 8), _buildActionButtons(theme), ], @@ -127,8 +153,9 @@ class CommunityStructureHeader extends StatelessWidget { children: [ _buildButton( label: "Save", - icon: const Icon(Icons.save, size: 18, color: ColorsManager.spaceColor), - onPressed: onSave, + icon: const Icon(Icons.save, + size: 18, color: ColorsManager.spaceColor), + onPressed: widget.onSave, theme: theme), ], ); @@ -159,7 +186,8 @@ class CommunityStructureHeader extends StatelessWidget { Flexible( child: Text( label, - style: theme.textTheme.bodySmall?.copyWith(color: ColorsManager.blackColor), + style: theme.textTheme.bodySmall + ?.copyWith(color: ColorsManager.blackColor), overflow: TextOverflow.ellipsis, maxLines: 1, ), diff --git a/lib/pages/spaces_management/widgets/community_structure_widget.dart b/lib/pages/spaces_management/widgets/community_structure_widget.dart index 22f0d218..3789b3c9 100644 --- a/lib/pages/spaces_management/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/widgets/community_structure_widget.dart @@ -119,7 +119,9 @@ class _CommunityStructureAreaState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ CommunityStructureHeader( + communities: widget.communities, communityName: widget.selectedCommunity?.name, + community: widget.selectedCommunity, isSave: isSave(spaces), isEditingName: isEditingName, nameController: _nameController, diff --git a/lib/pages/spaces_management/widgets/dialogs/create_community_dialog.dart b/lib/pages/spaces_management/widgets/dialogs/create_community_dialog.dart index 19ca8a99..e2208713 100644 --- a/lib/pages/spaces_management/widgets/dialogs/create_community_dialog.dart +++ b/lib/pages/spaces_management/widgets/dialogs/create_community_dialog.dart @@ -7,9 +7,16 @@ import 'package:syncrow_web/utils/color_manager.dart'; class CreateCommunityDialog extends StatefulWidget { final Function(String name, String description) onCreateCommunity; final List communities; + final bool isEditMode; + final CommunityModel? communityToEdit; - const CreateCommunityDialog( - {super.key, required this.onCreateCommunity, required this.communities}); + const CreateCommunityDialog({ + super.key, + required this.onCreateCommunity, + required this.communities, + required this.isEditMode, + this.communityToEdit, + }); @override CreateCommunityDialogState createState() => CreateCommunityDialogState(); @@ -19,6 +26,16 @@ class CreateCommunityDialogState extends State { String enteredName = ''; bool isNameFieldExist = false; bool isNameEmpty = false; + late TextEditingController nameController; + + @override + void initState() { + super.initState(); + // Initialize fields for edit mode or set defaults for create mode + nameController = TextEditingController( + text: widget.isEditMode ? widget.communityToEdit?.name ?? '' : '', + ); + } @override Widget build(BuildContext context) { @@ -52,9 +69,9 @@ class CreateCommunityDialogState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'Community Name', - style: TextStyle( + Text( + widget.isEditMode ? 'Edit Community Name' : 'Community Name', + style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), @@ -62,11 +79,14 @@ class CreateCommunityDialogState extends State { const SizedBox(height: 16), // Input field for the community name TextField( + controller: nameController, onChanged: (value) { setState(() { enteredName = value.trim(); isNameFieldExist = widget.communities.any( - (community) => community.name == enteredName, + (community) => + community.name == enteredName && + widget.communityToEdit != community, ); if (value.isEmpty) { isNameEmpty = true; @@ -99,7 +119,8 @@ class CreateCommunityDialogState extends State { borderSide: BorderSide( color: isNameFieldExist || isNameEmpty ? ColorsManager.red - : ColorsManager.boxColor, width: 1), + : ColorsManager.boxColor, + width: 1), borderRadius: BorderRadius.circular(10), ), focusedBorder: OutlineInputBorder( From 9bddd151bbaa841d156e6f7773f417482ba5e6a2 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 4 Dec 2024 11:05:46 +0400 Subject: [PATCH 002/175] added bloc for create community --- .../bloc/space_management_bloc.dart | 10 +- .../bloc/space_management_event.dart | 4 +- .../bloc/space_management_state.dart | 6 +- .../model/community_model.dart | 2 +- .../model/connection_model.dart | 2 +- .../{ => all_spaces}/model/product_model.dart | 0 .../model/selected_product_model.dart | 0 .../model/space_data_model.dart | 0 .../{ => all_spaces}/model/space_model.dart | 6 +- .../model/space_response_model.dart | 0 .../view/spaces_management_page.dart | 14 +- .../widgets/add_device_type_widget.dart | 6 +- .../widgets/blank_community_widget.dart | 10 +- .../community_structure_header_widget.dart | 9 +- .../widgets/community_structure_widget.dart | 26 +-- .../widgets/community_tile.dart | 0 .../widgets/counter_widget.dart | 0 .../widgets/curved_line_painter.dart | 2 +- .../widgets/dialogs/create_space_dialog.dart | 12 +- .../widgets/dialogs/delete_dialogue.dart | 0 .../dialogs/icon_selection_dialog.dart | 0 .../gradient_canvas_border_widget.dart | 0 .../widgets/hoverable_button.dart | 0 .../widgets/loaded_space_widget.dart | 12 +- .../widgets/plus_button_widget.dart | 0 .../widgets/sidebar_widget.dart | 12 +- .../widgets/space_card_widget.dart | 0 .../widgets/space_container_widget.dart | 0 .../widgets/space_tile_widget.dart | 0 .../widgets/space_widget.dart | 0 .../bloc/community_dialog_bloc.dart | 21 ++ .../bloc/community_dialog_event.dart | 15 ++ .../bloc/community_dialog_state.dart | 21 ++ .../view/create_community_dialog.dart | 166 +++++++++++++++ .../dialogs/create_community_dialog.dart | 198 ------------------ lib/services/product_api.dart | 2 +- lib/services/space_mana_api.dart | 8 +- lib/utils/app_routes.dart | 4 +- 38 files changed, 297 insertions(+), 271 deletions(-) rename lib/pages/spaces_management/{ => all_spaces}/bloc/space_management_bloc.dart (95%) rename lib/pages/spaces_management/{ => all_spaces}/bloc/space_management_event.dart (93%) rename lib/pages/spaces_management/{ => all_spaces}/bloc/space_management_state.dart (82%) rename lib/pages/spaces_management/{ => all_spaces}/model/community_model.dart (93%) rename lib/pages/spaces_management/{ => all_spaces}/model/connection_model.dart (89%) rename lib/pages/spaces_management/{ => all_spaces}/model/product_model.dart (100%) rename lib/pages/spaces_management/{ => all_spaces}/model/selected_product_model.dart (100%) rename lib/pages/spaces_management/{ => all_spaces}/model/space_data_model.dart (100%) rename lib/pages/spaces_management/{ => all_spaces}/model/space_model.dart (93%) rename lib/pages/spaces_management/{ => all_spaces}/model/space_response_model.dart (100%) rename lib/pages/spaces_management/{ => all_spaces}/view/spaces_management_page.dart (77%) rename lib/pages/spaces_management/{ => all_spaces}/widgets/add_device_type_widget.dart (96%) rename lib/pages/spaces_management/{ => all_spaces}/widgets/blank_community_widget.dart (85%) rename lib/pages/spaces_management/{ => all_spaces}/widgets/community_structure_header_widget.dart (94%) rename lib/pages/spaces_management/{ => all_spaces}/widgets/community_structure_widget.dart (93%) rename lib/pages/spaces_management/{ => all_spaces}/widgets/community_tile.dart (100%) rename lib/pages/spaces_management/{ => all_spaces}/widgets/counter_widget.dart (100%) rename lib/pages/spaces_management/{ => all_spaces}/widgets/curved_line_painter.dart (95%) rename lib/pages/spaces_management/{ => all_spaces}/widgets/dialogs/create_space_dialog.dart (96%) rename lib/pages/spaces_management/{ => all_spaces}/widgets/dialogs/delete_dialogue.dart (100%) rename lib/pages/spaces_management/{ => all_spaces}/widgets/dialogs/icon_selection_dialog.dart (100%) rename lib/pages/spaces_management/{ => all_spaces}/widgets/gradient_canvas_border_widget.dart (100%) rename lib/pages/spaces_management/{ => all_spaces}/widgets/hoverable_button.dart (100%) rename lib/pages/spaces_management/{ => all_spaces}/widgets/loaded_space_widget.dart (73%) rename lib/pages/spaces_management/{ => all_spaces}/widgets/plus_button_widget.dart (100%) rename lib/pages/spaces_management/{ => all_spaces}/widgets/sidebar_widget.dart (92%) rename lib/pages/spaces_management/{ => all_spaces}/widgets/space_card_widget.dart (100%) rename lib/pages/spaces_management/{ => all_spaces}/widgets/space_container_widget.dart (100%) rename lib/pages/spaces_management/{ => all_spaces}/widgets/space_tile_widget.dart (100%) rename lib/pages/spaces_management/{ => all_spaces}/widgets/space_widget.dart (100%) create mode 100644 lib/pages/spaces_management/create_community/bloc/community_dialog_bloc.dart create mode 100644 lib/pages/spaces_management/create_community/bloc/community_dialog_event.dart create mode 100644 lib/pages/spaces_management/create_community/bloc/community_dialog_state.dart create mode 100644 lib/pages/spaces_management/create_community/view/create_community_dialog.dart delete mode 100644 lib/pages/spaces_management/widgets/dialogs/create_community_dialog.dart diff --git a/lib/pages/spaces_management/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart similarity index 95% rename from lib/pages/spaces_management/bloc/space_management_bloc.dart rename to lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index dc661dab..a171f2a7 100644 --- a/lib/pages/spaces_management/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -1,9 +1,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; -import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart'; -import 'package:syncrow_web/pages/spaces_management/bloc/space_management_state.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'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.dart'; import 'package:syncrow_web/services/product_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; diff --git a/lib/pages/spaces_management/bloc/space_management_event.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart similarity index 93% rename from lib/pages/spaces_management/bloc/space_management_event.dart rename to lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart index 7b95f262..9e3dcc74 100644 --- a/lib/pages/spaces_management/bloc/space_management_event.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart @@ -1,7 +1,7 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; // Import for Offset +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 for Offset abstract class SpaceManagementEvent extends Equatable { const SpaceManagementEvent(); diff --git a/lib/pages/spaces_management/bloc/space_management_state.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart similarity index 82% rename from lib/pages/spaces_management/bloc/space_management_state.dart rename to lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart index 0a1cfe4d..eca8c16f 100644 --- a/lib/pages/spaces_management/bloc/space_management_state.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart @@ -1,7 +1,7 @@ import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/space_model.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'; abstract class SpaceManagementState extends Equatable { const SpaceManagementState(); diff --git a/lib/pages/spaces_management/model/community_model.dart b/lib/pages/spaces_management/all_spaces/model/community_model.dart similarity index 93% rename from lib/pages/spaces_management/model/community_model.dart rename to lib/pages/spaces_management/all_spaces/model/community_model.dart index b61b780b..14d9d729 100644 --- a/lib/pages/spaces_management/model/community_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/community_model.dart @@ -1,5 +1,5 @@ import 'package:syncrow_web/pages/auth/model/region_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; class CommunityModel { final String uuid; diff --git a/lib/pages/spaces_management/model/connection_model.dart b/lib/pages/spaces_management/all_spaces/model/connection_model.dart similarity index 89% rename from lib/pages/spaces_management/model/connection_model.dart rename to lib/pages/spaces_management/all_spaces/model/connection_model.dart index 650781d6..a774efe2 100644 --- a/lib/pages/spaces_management/model/connection_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/connection_model.dart @@ -1,4 +1,4 @@ -import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; class Connection { final SpaceModel startSpace; diff --git a/lib/pages/spaces_management/model/product_model.dart b/lib/pages/spaces_management/all_spaces/model/product_model.dart similarity index 100% rename from lib/pages/spaces_management/model/product_model.dart rename to lib/pages/spaces_management/all_spaces/model/product_model.dart diff --git a/lib/pages/spaces_management/model/selected_product_model.dart b/lib/pages/spaces_management/all_spaces/model/selected_product_model.dart similarity index 100% rename from lib/pages/spaces_management/model/selected_product_model.dart rename to lib/pages/spaces_management/all_spaces/model/selected_product_model.dart diff --git a/lib/pages/spaces_management/model/space_data_model.dart b/lib/pages/spaces_management/all_spaces/model/space_data_model.dart similarity index 100% rename from lib/pages/spaces_management/model/space_data_model.dart rename to lib/pages/spaces_management/all_spaces/model/space_data_model.dart diff --git a/lib/pages/spaces_management/model/space_model.dart b/lib/pages/spaces_management/all_spaces/model/space_model.dart similarity index 93% rename from lib/pages/spaces_management/model/space_model.dart rename to lib/pages/spaces_management/all_spaces/model/space_model.dart index 3e38d931..df6550f8 100644 --- a/lib/pages/spaces_management/model/space_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/space_model.dart @@ -1,7 +1,7 @@ import 'dart:ui'; -import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/connection_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:uuid/uuid.dart'; diff --git a/lib/pages/spaces_management/model/space_response_model.dart b/lib/pages/spaces_management/all_spaces/model/space_response_model.dart similarity index 100% rename from lib/pages/spaces_management/model/space_response_model.dart rename to lib/pages/spaces_management/all_spaces/model/space_response_model.dart diff --git a/lib/pages/spaces_management/view/spaces_management_page.dart b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart similarity index 77% rename from lib/pages/spaces_management/view/spaces_management_page.dart rename to lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart index 8b37b876..8cad58b2 100644 --- a/lib/pages/spaces_management/view/spaces_management_page.dart +++ b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; -import 'package:syncrow_web/pages/spaces_management/bloc/space_management_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart'; -import 'package:syncrow_web/pages/spaces_management/bloc/space_management_state.dart'; -import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/loaded_space_widget.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/bloc/space_management_state.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'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart'; import 'package:syncrow_web/services/product_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; diff --git a/lib/pages/spaces_management/widgets/add_device_type_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart similarity index 96% rename from lib/pages/spaces_management/widgets/add_device_type_widget.dart rename to lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart index 08c08997..40759b58 100644 --- a/lib/pages/spaces_management/widgets/add_device_type_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; -import 'package:syncrow_web/pages/spaces_management/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/selected_product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/counter_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.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'; diff --git a/lib/pages/spaces_management/widgets/blank_community_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/blank_community_widget.dart similarity index 85% rename from lib/pages/spaces_management/widgets/blank_community_widget.dart rename to lib/pages/spaces_management/all_spaces/widgets/blank_community_widget.dart index a57f3c18..999c27bd 100644 --- a/lib/pages/spaces_management/widgets/blank_community_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/blank_community_widget.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/bloc/space_management_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart'; -import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/dialogs/create_community_dialog.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/create_community/view/create_community_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class BlankCommunityWidget extends StatefulWidget { @@ -73,7 +73,7 @@ class _BlankCommunityWidgetState extends State { context: parentContext, builder: (context) => CreateCommunityDialog( isEditMode: false, - communities: widget.communities, + existingCommunityNames: widget.communities.map((community) => community.name).toList(), onCreateCommunity: (String communityName, String description) { parentContext.read().add( CreateCommunityEvent( diff --git a/lib/pages/spaces_management/widgets/community_structure_header_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart similarity index 94% rename from lib/pages/spaces_management/widgets/community_structure_header_widget.dart rename to lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart index 72d6228e..63306581 100644 --- a/lib/pages/spaces_management/widgets/community_structure_header_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; -import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/dialogs/create_community_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; +import 'package:syncrow_web/pages/spaces_management/create_community/view/create_community_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -76,8 +76,9 @@ class _CommunityStructureHeaderState extends State { context: parentContext, builder: (context) => CreateCommunityDialog( isEditMode: true, - communities: widget.communities, - communityToEdit: widget.community, + existingCommunityNames: + widget.communities.map((community) => community.name).toList(), + initialName: widget.community?.name ?? '', onCreateCommunity: (String communityName, String description) { widget.onNameSubmitted(communityName); }, diff --git a/lib/pages/spaces_management/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart similarity index 93% rename from lib/pages/spaces_management/widgets/community_structure_widget.dart rename to lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 3789b3c9..c11563da 100644 --- a/lib/pages/spaces_management/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -4,19 +4,19 @@ import 'package:flutter_bloc/flutter_bloc.dart'; // Syncrow project imports import 'package:syncrow_web/pages/common/buttons/add_space_button.dart'; -import 'package:syncrow_web/pages/spaces_management/bloc/space_management_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart'; -import 'package:syncrow_web/pages/spaces_management/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/selected_product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/connection_model.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/blank_community_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/community_structure_header_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/dialogs/create_space_dialog.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/curved_line_painter.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/space_card_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/space_container_widget.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/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/blank_community_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/curved_line_painter.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_card_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_container_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CommunityStructureArea extends StatefulWidget { diff --git a/lib/pages/spaces_management/widgets/community_tile.dart b/lib/pages/spaces_management/all_spaces/widgets/community_tile.dart similarity index 100% rename from lib/pages/spaces_management/widgets/community_tile.dart rename to lib/pages/spaces_management/all_spaces/widgets/community_tile.dart diff --git a/lib/pages/spaces_management/widgets/counter_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/counter_widget.dart similarity index 100% rename from lib/pages/spaces_management/widgets/counter_widget.dart rename to lib/pages/spaces_management/all_spaces/widgets/counter_widget.dart diff --git a/lib/pages/spaces_management/widgets/curved_line_painter.dart b/lib/pages/spaces_management/all_spaces/widgets/curved_line_painter.dart similarity index 95% rename from lib/pages/spaces_management/widgets/curved_line_painter.dart rename to lib/pages/spaces_management/all_spaces/widgets/curved_line_painter.dart index 263f0edc..2b85acfd 100644 --- a/lib/pages/spaces_management/widgets/curved_line_painter.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/curved_line_painter.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/spaces_management/model/connection_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CurvedLinePainter extends CustomPainter { diff --git a/lib/pages/spaces_management/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart similarity index 96% rename from lib/pages/spaces_management/widgets/dialogs/create_space_dialog.dart rename to lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 028729b8..e0853e3a 100644 --- a/lib/pages/spaces_management/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.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/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/selected_product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/add_device_type_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/dialogs/icon_selection_dialog.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/hoverable_button.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/hoverable_button.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/space_icon_const.dart'; diff --git a/lib/pages/spaces_management/widgets/dialogs/delete_dialogue.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/delete_dialogue.dart similarity index 100% rename from lib/pages/spaces_management/widgets/dialogs/delete_dialogue.dart rename to lib/pages/spaces_management/all_spaces/widgets/dialogs/delete_dialogue.dart diff --git a/lib/pages/spaces_management/widgets/dialogs/icon_selection_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart similarity index 100% rename from lib/pages/spaces_management/widgets/dialogs/icon_selection_dialog.dart rename to lib/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart diff --git a/lib/pages/spaces_management/widgets/gradient_canvas_border_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/gradient_canvas_border_widget.dart similarity index 100% rename from lib/pages/spaces_management/widgets/gradient_canvas_border_widget.dart rename to lib/pages/spaces_management/all_spaces/widgets/gradient_canvas_border_widget.dart diff --git a/lib/pages/spaces_management/widgets/hoverable_button.dart b/lib/pages/spaces_management/all_spaces/widgets/hoverable_button.dart similarity index 100% rename from lib/pages/spaces_management/widgets/hoverable_button.dart rename to lib/pages/spaces_management/all_spaces/widgets/hoverable_button.dart diff --git a/lib/pages/spaces_management/widgets/loaded_space_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart similarity index 73% rename from lib/pages/spaces_management/widgets/loaded_space_widget.dart rename to lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart index 6b149701..7ce56914 100644 --- a/lib/pages/spaces_management/widgets/loaded_space_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/community_structure_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/gradient_canvas_border_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/sidebar_widget.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'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/gradient_canvas_border_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart'; class LoadedSpaceView extends StatefulWidget { final List communities; diff --git a/lib/pages/spaces_management/widgets/plus_button_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/plus_button_widget.dart similarity index 100% rename from lib/pages/spaces_management/widgets/plus_button_widget.dart rename to lib/pages/spaces_management/all_spaces/widgets/plus_button_widget.dart diff --git a/lib/pages/spaces_management/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart similarity index 92% rename from lib/pages/spaces_management/widgets/sidebar_widget.dart rename to lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index e11be2c2..a58d73e9 100644 --- a/lib/pages/spaces_management/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/common/search_bar.dart'; -import 'package:syncrow_web/pages/spaces_management/bloc/space_management_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart'; -import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/community_tile.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/space_tile_widget.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/space_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_tile.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/style.dart'; diff --git a/lib/pages/spaces_management/widgets/space_card_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/space_card_widget.dart similarity index 100% rename from lib/pages/spaces_management/widgets/space_card_widget.dart rename to lib/pages/spaces_management/all_spaces/widgets/space_card_widget.dart diff --git a/lib/pages/spaces_management/widgets/space_container_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/space_container_widget.dart similarity index 100% rename from lib/pages/spaces_management/widgets/space_container_widget.dart rename to lib/pages/spaces_management/all_spaces/widgets/space_container_widget.dart diff --git a/lib/pages/spaces_management/widgets/space_tile_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart similarity index 100% rename from lib/pages/spaces_management/widgets/space_tile_widget.dart rename to lib/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart diff --git a/lib/pages/spaces_management/widgets/space_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/space_widget.dart similarity index 100% rename from lib/pages/spaces_management/widgets/space_widget.dart rename to lib/pages/spaces_management/all_spaces/widgets/space_widget.dart diff --git a/lib/pages/spaces_management/create_community/bloc/community_dialog_bloc.dart b/lib/pages/spaces_management/create_community/bloc/community_dialog_bloc.dart new file mode 100644 index 00000000..724ed51a --- /dev/null +++ b/lib/pages/spaces_management/create_community/bloc/community_dialog_bloc.dart @@ -0,0 +1,21 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'community_dialog_event.dart'; +import 'community_dialog_state.dart'; + +class CommunityDialogBloc extends Bloc { + final List existingCommunityNames; + + CommunityDialogBloc(this.existingCommunityNames) + : super(CommunityDialogInitial()) { + on((event, emit) { + final trimmedName = event.name.trim(); + final isNameValid = !existingCommunityNames.contains(trimmedName); + final isNameEmpty = trimmedName.isEmpty; + + emit(CommunityNameValidationState( + isNameValid: isNameValid, + isNameEmpty: isNameEmpty, + )); + }); + } +} diff --git a/lib/pages/spaces_management/create_community/bloc/community_dialog_event.dart b/lib/pages/spaces_management/create_community/bloc/community_dialog_event.dart new file mode 100644 index 00000000..74f4fa5c --- /dev/null +++ b/lib/pages/spaces_management/create_community/bloc/community_dialog_event.dart @@ -0,0 +1,15 @@ +import 'package:equatable/equatable.dart'; + +abstract class CommunityDialogEvent extends Equatable { + @override + List get props => []; +} + +class ValidateCommunityNameEvent extends CommunityDialogEvent { + final String name; + + ValidateCommunityNameEvent(this.name); + + @override + List get props => [name]; +} diff --git a/lib/pages/spaces_management/create_community/bloc/community_dialog_state.dart b/lib/pages/spaces_management/create_community/bloc/community_dialog_state.dart new file mode 100644 index 00000000..8b61c1c7 --- /dev/null +++ b/lib/pages/spaces_management/create_community/bloc/community_dialog_state.dart @@ -0,0 +1,21 @@ +import 'package:equatable/equatable.dart'; + +abstract class CommunityDialogState extends Equatable { + @override + List get props => []; +} + +class CommunityDialogInitial extends CommunityDialogState {} + +class CommunityNameValidationState extends CommunityDialogState { + final bool isNameValid; + final bool isNameEmpty; + + CommunityNameValidationState({ + required this.isNameValid, + required this.isNameEmpty, + }); + + @override + List get props => [isNameValid, isNameEmpty]; +} diff --git a/lib/pages/spaces_management/create_community/view/create_community_dialog.dart b/lib/pages/spaces_management/create_community/view/create_community_dialog.dart new file mode 100644 index 00000000..94a8c054 --- /dev/null +++ b/lib/pages/spaces_management/create_community/view/create_community_dialog.dart @@ -0,0 +1,166 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/create_community/bloc/community_dialog_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/create_community/bloc/community_dialog_event.dart'; +import 'package:syncrow_web/pages/spaces_management/create_community/bloc/community_dialog_state.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; + +class CreateCommunityDialog extends StatelessWidget { + final Function(String name, String description) onCreateCommunity; + final List existingCommunityNames; + final bool isEditMode; + final String? initialName; + + const CreateCommunityDialog({ + super.key, + required this.onCreateCommunity, + required this.existingCommunityNames, + required this.isEditMode, + this.initialName, + }); + + @override + Widget build(BuildContext context) { + final nameController = + TextEditingController(text: isEditMode ? initialName : ''); + + return BlocProvider( + create: (_) => CommunityDialogBloc(existingCommunityNames), + child: Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + backgroundColor: ColorsManager.transparentColor, + child: Stack( + children: [ + Container( + width: MediaQuery.of(context).size.width * 0.3, + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: ColorsManager.blackColor.withOpacity(0.25), + blurRadius: 20, + spreadRadius: 5, + offset: const Offset(0, 5), + ), + ], + ), + child: SingleChildScrollView( + child: BlocBuilder( + builder: (context, state) { + bool isNameValid = true; + bool isNameEmpty = false; + + if (state is CommunityNameValidationState) { + isNameValid = state.isNameValid; + isNameEmpty = state.isNameEmpty; + } + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + isEditMode ? 'Edit Community Name' : 'Community Name', + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + TextField( + controller: nameController, + onChanged: (value) { + context + .read() + .add(ValidateCommunityNameEvent(value)); + }, + style: const TextStyle( + color: ColorsManager.blackColor, + ), + decoration: InputDecoration( + hintText: 'Please enter the community name', + filled: true, + fillColor: ColorsManager.boxColor, + border: OutlineInputBorder( + borderSide: BorderSide( + color: isNameValid && !isNameEmpty + ? ColorsManager.boxColor + : ColorsManager.red, + width: 1, + ), + borderRadius: BorderRadius.circular(10), + ), + ), + ), + if (!isNameValid) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + '*Name already exists.', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.red), + ), + ), + if (isNameEmpty) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + '*Name should not be empty.', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.red), + ), + ), + const SizedBox(height: 24), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: CancelButton( + label: 'Cancel', + onPressed: () => Navigator.of(context).pop(), + ), + ), + const SizedBox(width: 16), + Expanded( + child: DefaultButton( + onPressed: () { + if (isNameValid && !isNameEmpty) { + onCreateCommunity( + nameController.text.trim(), + "", + ); + Navigator.of(context).pop(); + } + }, + backgroundColor: isNameValid && !isNameEmpty + ? ColorsManager.secondaryColor + : ColorsManager.lightGrayColor, + borderRadius: 10, + foregroundColor: ColorsManager.whiteColors, + child: const Text('OK'), + ), + ), + ], + ), + ], + ); + }, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/widgets/dialogs/create_community_dialog.dart b/lib/pages/spaces_management/widgets/dialogs/create_community_dialog.dart deleted file mode 100644 index e2208713..00000000 --- a/lib/pages/spaces_management/widgets/dialogs/create_community_dialog.dart +++ /dev/null @@ -1,198 +0,0 @@ -import 'package:flutter/material.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/model/community_model.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; - -class CreateCommunityDialog extends StatefulWidget { - final Function(String name, String description) onCreateCommunity; - final List communities; - final bool isEditMode; - final CommunityModel? communityToEdit; - - const CreateCommunityDialog({ - super.key, - required this.onCreateCommunity, - required this.communities, - required this.isEditMode, - this.communityToEdit, - }); - - @override - CreateCommunityDialogState createState() => CreateCommunityDialogState(); -} - -class CreateCommunityDialogState extends State { - String enteredName = ''; - bool isNameFieldExist = false; - bool isNameEmpty = false; - late TextEditingController nameController; - - @override - void initState() { - super.initState(); - // Initialize fields for edit mode or set defaults for create mode - nameController = TextEditingController( - text: widget.isEditMode ? widget.communityToEdit?.name ?? '' : '', - ); - } - - @override - Widget build(BuildContext context) { - final screenWidth = MediaQuery.of(context).size.width; - - return Dialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - backgroundColor: - ColorsManager.transparentColor, // Transparent for shadow effect - child: Stack( - children: [ - // Background container with shadow and rounded corners - Container( - width: screenWidth * 0.3, - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: ColorsManager.whiteColors, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: ColorsManager.blackColor.withOpacity(0.25), - blurRadius: 20, - spreadRadius: 5, - offset: const Offset(0, 5), - ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.isEditMode ? 'Edit Community Name' : 'Community Name', - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 16), - // Input field for the community name - TextField( - controller: nameController, - onChanged: (value) { - setState(() { - enteredName = value.trim(); - isNameFieldExist = widget.communities.any( - (community) => - community.name == enteredName && - widget.communityToEdit != community, - ); - if (value.isEmpty) { - isNameEmpty = true; - } else { - isNameEmpty = false; - } - }); - }, - style: const TextStyle( - color: ColorsManager.blackColor, - ), - decoration: InputDecoration( - hintText: 'Please enter the community name', - filled: true, - fillColor: ColorsManager.boxColor, - hintStyle: const TextStyle( - fontSize: 14, - color: ColorsManager.grayBorder, - fontWeight: FontWeight.w400, - ), - border: OutlineInputBorder( - borderSide: BorderSide( - color: isNameFieldExist || isNameEmpty - ? ColorsManager.red - : ColorsManager.boxColor, - width: 1), - borderRadius: BorderRadius.circular(10), - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: isNameFieldExist || isNameEmpty - ? ColorsManager.red - : ColorsManager.boxColor, - width: 1), - borderRadius: BorderRadius.circular(10), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: BorderSide( - color: isNameFieldExist || isNameEmpty - ? ColorsManager.red - : ColorsManager.boxColor, - width: 1), - ), - ), - ), - if (isNameFieldExist) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - '*Name already exists.', - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: ColorsManager.red), - ), - ), - if (isNameEmpty) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - '*Name should not be empty.', - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: ColorsManager.red), - ), - ), - const SizedBox(height: 24), - // Action buttons - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: CancelButton( - label: 'Cancel', - onPressed: () => Navigator.of(context).pop(), - ), - ), - const SizedBox(width: 16), - Expanded( - child: DefaultButton( - onPressed: () { - if (enteredName.isNotEmpty && !isNameFieldExist) { - widget.onCreateCommunity( - enteredName, - "", - ); - Navigator.of(context).pop(); - } - }, - backgroundColor: isNameFieldExist || isNameEmpty - ? ColorsManager.lightGrayColor - : ColorsManager.secondaryColor, - borderRadius: 10, - foregroundColor: ColorsManager.whiteColors, - child: const Text('OK'), - ), - ), - ], - ), - ], - ), - ), - ], - ), - ); - } -} diff --git a/lib/services/product_api.dart b/lib/services/product_api.dart index f33a4135..02c9f143 100644 --- a/lib/services/product_api.dart +++ b/lib/services/product_api.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/spaces_management/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 5d2464e6..55875bfa 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/selected_product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/space_response_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; diff --git a/lib/utils/app_routes.dart b/lib/utils/app_routes.dart index 20a89e21..31329d8a 100644 --- a/lib/utils/app_routes.dart +++ b/lib/utils/app_routes.dart @@ -3,7 +3,7 @@ import 'package:syncrow_web/pages/access_management/view/access_management.dart' import 'package:syncrow_web/pages/auth/view/login_page.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_page.dart'; import 'package:syncrow_web/pages/home/view/home_page.dart'; -import 'package:syncrow_web/pages/spaces_management/view/spaces_management_page.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/view/spaces_management_page.dart'; import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; import 'package:syncrow_web/utils/constants/routes_const.dart'; @@ -32,7 +32,7 @@ class AppRoutes { ), GoRoute( path: RoutesConst.spacesManagementPage, - builder: (context, state) => SpaceManagementPage()), + builder: (context, state) => const SpaceManagementPage()), ]; } } From d423a3eb5949a5f57f01d8b97823af9702d8ae05 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 4 Dec 2024 11:14:02 +0400 Subject: [PATCH 003/175] added focused border --- .../view/create_community_dialog.dart | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/pages/spaces_management/create_community/view/create_community_dialog.dart b/lib/pages/spaces_management/create_community/view/create_community_dialog.dart index 94a8c054..1a5460d1 100644 --- a/lib/pages/spaces_management/create_community/view/create_community_dialog.dart +++ b/lib/pages/spaces_management/create_community/view/create_community_dialog.dart @@ -67,12 +67,9 @@ class CreateCommunityDialog extends StatelessWidget { children: [ Text( isEditMode ? 'Edit Community Name' : 'Community Name', - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), + style: Theme.of(context).textTheme.headlineMedium, ), - const SizedBox(height: 16), + const SizedBox(height: 18), TextField( controller: nameController, onChanged: (value) { @@ -87,7 +84,7 @@ class CreateCommunityDialog extends StatelessWidget { hintText: 'Please enter the community name', filled: true, fillColor: ColorsManager.boxColor, - border: OutlineInputBorder( + enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: isNameValid && !isNameEmpty ? ColorsManager.boxColor @@ -96,6 +93,13 @@ class CreateCommunityDialog extends StatelessWidget { ), borderRadius: BorderRadius.circular(10), ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: const BorderSide( + color: ColorsManager.boxColor, + width: 1.5, + ), + ), ), ), if (!isNameValid) From 26f50d59ddebf12adf52a3ca963b40c98ed5b633 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Thu, 5 Dec 2024 11:19:22 +0300 Subject: [PATCH 004/175] Bug fixes --- .../bloc/routine_bloc/routine_bloc.dart | 20 +-- .../routiens/widgets/then_container.dart | 116 +++++++----------- 2 files changed, 56 insertions(+), 80 deletions(-) diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index c1f3b39d..89781032 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -1050,7 +1050,9 @@ class RoutineBloc extends Bloc { final automationDetails = await SceneApi.getAutomationDetails(event.automationId); - final Map> deviceCards = {}; + final Map> deviceIfCards = {}; + final Map> deviceThenCards = {}; + final Map> updatedFunctions = Map>.from(state.selectedFunctions); @@ -1066,8 +1068,8 @@ class RoutineBloc extends Bloc { final deviceId = condition.entityId; - if (!deviceCards.containsKey(deviceId)) { - deviceCards[deviceId] = { + if (!deviceIfCards.containsKey(deviceId)) { + deviceIfCards[deviceId] = { 'entityId': condition.entityId, 'deviceId': condition.entityId, 'uniqueCustomId': const Uuid().v4(), @@ -1080,7 +1082,7 @@ class RoutineBloc extends Bloc { }; } - final cardData = deviceCards[deviceId]!; + final cardData = deviceIfCards[deviceId]!; final uniqueCustomId = cardData['uniqueCustomId'].toString(); if (!updatedFunctions.containsKey(uniqueCustomId)) { @@ -1117,8 +1119,8 @@ class RoutineBloc extends Bloc { final deviceId = action.actionExecutor == 'delay' ? '${action.entityId}_delay' : action.entityId; - if (!deviceCards.containsKey(deviceId)) { - deviceCards[deviceId] = { + if (!deviceThenCards.containsKey(deviceId)) { + deviceThenCards[deviceId] = { 'entityId': action.entityId, 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId, 'uniqueCustomId': const Uuid().v4(), @@ -1143,7 +1145,7 @@ class RoutineBloc extends Bloc { }; } - final cardData = deviceCards[deviceId]!; + final cardData = deviceThenCards[deviceId]!; final uniqueCustomId = cardData['uniqueCustomId'].toString(); if (!updatedFunctions.containsKey(uniqueCustomId)) { @@ -1192,8 +1194,8 @@ class RoutineBloc extends Bloc { } } - final ifItems = deviceCards.values.where((card) => card['type'] == 'condition').toList(); - final thenItems = deviceCards.values + final ifItems = deviceIfCards.values.where((card) => card['type'] == 'condition').toList(); + final thenItems = deviceThenCards.values .where((card) => card['type'] == 'action' || card['type'] == 'automation' || card['type'] == 'scene') .toList(); diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index 8a12e443..4746561a 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -26,9 +26,7 @@ class ThenContainer extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('THEN', - style: TextStyle( - fontSize: 18, fontWeight: FontWeight.bold)), + const Text('THEN', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 16), state.isLoading && state.isUpdate == true ? const Center( @@ -41,17 +39,12 @@ class ThenContainer extends StatelessWidget { state.thenItems.length, (index) => GestureDetector( onTap: () async { - if (state.thenItems[index] - ['deviceId'] == - 'delay') { - final result = await DelayHelper - .showDelayPickerDialog(context, - state.thenItems[index]); + if (state.thenItems[index]['deviceId'] == 'delay') { + final result = await DelayHelper.showDelayPickerDialog( + context, state.thenItems[index]); if (result != null) { - context - .read() - .add(AddToThenContainer({ + context.read().add(AddToThenContainer({ ...state.thenItems[index], 'imagePath': Assets.delay, 'title': 'Delay', @@ -60,79 +53,58 @@ class ThenContainer extends StatelessWidget { return; } - if (state.thenItems[index]['type'] == - 'automation') { + if (state.thenItems[index]['type'] == 'automation') { final result = await showDialog( context: context, - builder: (BuildContext context) => - AutomationDialog( + builder: (BuildContext context) => AutomationDialog( automationName: - state.thenItems[index] - ['name'] ?? - 'Automation', + state.thenItems[index]['name'] ?? 'Automation', automationId: - state.thenItems[index] - ['deviceId'] ?? - '', - uniqueCustomId: - state.thenItems[index] - ['uniqueCustomId'], + state.thenItems[index]['deviceId'] ?? '', + uniqueCustomId: state.thenItems[index] + ['uniqueCustomId'], ), ); if (result != null) { - context - .read() - .add(AddToThenContainer({ + context.read().add(AddToThenContainer({ ...state.thenItems[index], - 'imagePath': - Assets.automation, - 'title': - state.thenItems[index] - ['name'] ?? - state.thenItems[index] - ['title'], + 'imagePath': Assets.automation, + 'title': state.thenItems[index]['name'] ?? + state.thenItems[index]['title'], })); } return; } - final result = await DeviceDialogHelper - .showDeviceDialog( - context, state.thenItems[index], - removeComparetors: true); + final result = await DeviceDialogHelper.showDeviceDialog( + context, state.thenItems[index], + removeComparetors: true); if (result != null) { - context.read().add( - AddToThenContainer( - state.thenItems[index])); + context + .read() + .add(AddToThenContainer(state.thenItems[index])); } else if (!['AC', '1G', '2G', '3G'] - .contains(state.thenItems[index] - ['productType'])) { - context.read().add( - AddToThenContainer( - state.thenItems[index])); + .contains(state.thenItems[index]['productType'])) { + context + .read() + .add(AddToThenContainer(state.thenItems[index])); } }, child: DraggableCard( - imagePath: state.thenItems[index] - ['imagePath'] ?? - '', - title: state.thenItems[index] - ['title'] ?? - '', + imagePath: state.thenItems[index]['imagePath'] ?? '', + title: state.thenItems[index]['title'] ?? '', deviceData: state.thenItems[index], - padding: const EdgeInsets.symmetric( - horizontal: 4, vertical: 8), + padding: + const EdgeInsets.symmetric(horizontal: 4, vertical: 8), isFromThen: true, isFromIf: false, onRemove: () { - context.read().add( - RemoveDragCard( - index: index, - isFromThen: true, - key: state.thenItems[index] - ['uniqueCustomId'])); + context.read().add(RemoveDragCard( + index: index, + isFromThen: true, + key: state.thenItems[index]['uniqueCustomId'])); }, ), ))), @@ -151,9 +123,14 @@ class ThenContainer extends StatelessWidget { return; } + if (state.automationId == mutableData['deviceId'] || + state.sceneId == mutableData['deviceId']) { + return; + } + if (mutableData['type'] == 'automation') { - int index = state.thenItems.indexWhere( - (item) => item['deviceId'] == mutableData['deviceId']); + int index = + state.thenItems.indexWhere((item) => item['deviceId'] == mutableData['deviceId']); if (index != -1) { return; } @@ -178,8 +155,8 @@ class ThenContainer extends StatelessWidget { } if (mutableData['type'] == 'tap_to_run' && state.isAutomation) { - int index = state.thenItems.indexWhere( - (item) => item['deviceId'] == mutableData['deviceId']); + int index = + state.thenItems.indexWhere((item) => item['deviceId'] == mutableData['deviceId']); if (index != -1) { return; } @@ -197,8 +174,7 @@ class ThenContainer extends StatelessWidget { } if (mutableData['deviceId'] == 'delay') { - final result = - await DelayHelper.showDelayPickerDialog(context, mutableData); + final result = await DelayHelper.showDelayPickerDialog(context, mutableData); if (result != null) { context.read().add(AddToThenContainer({ @@ -210,13 +186,11 @@ class ThenContainer extends StatelessWidget { return; } - final result = await DeviceDialogHelper.showDeviceDialog( - context, mutableData, + final result = await DeviceDialogHelper.showDeviceDialog(context, mutableData, removeComparetors: true); if (result != null) { context.read().add(AddToThenContainer(mutableData)); - } else if (!['AC', '1G', '2G', '3G'] - .contains(mutableData['productType'])) { + } else if (!['AC', '1G', '2G', '3G'].contains(mutableData['productType'])) { context.read().add(AddToThenContainer(mutableData)); } }, From 246098b83a5a535b80b36debb2a9e9520c6e3efc Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Thu, 5 Dec 2024 12:42:53 +0300 Subject: [PATCH 005/175] Read icon from fetch scene api --- .../bloc/routine_bloc/routine_bloc.dart | 3 ++ .../routiens/helper/save_routine_helper.dart | 2 +- .../models/routine_details_model.dart | 52 +++++++++---------- lib/pages/routiens/widgets/dragable_card.dart | 22 ++++---- 4 files changed, 37 insertions(+), 42 deletions(-) diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 89781032..a90ea116 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -758,6 +758,8 @@ class RoutineBloc extends Bloc { thenItems: [], selectedFunctions: {}, scenes: [], + sceneId: '', + automationId: '', automations: [], isLoading: false, errorMessage: '', @@ -1142,6 +1144,7 @@ class RoutineBloc extends Bloc { : action.type == 'automation' ? 'automation' : 'action', + 'icon': action.icon ?? '' }; } diff --git a/lib/pages/routiens/helper/save_routine_helper.dart b/lib/pages/routiens/helper/save_routine_helper.dart index 280c539a..d73a69a7 100644 --- a/lib/pages/routiens/helper/save_routine_helper.dart +++ b/lib/pages/routiens/helper/save_routine_helper.dart @@ -101,7 +101,7 @@ class SaveRoutineHelper { final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; return ListTile( - leading: item['type'] == 'tap_to_run' + leading: item['type'] == 'tap_to_run' || item['type'] == 'scene' ? Image.memory( base64Decode(item['icon']), width: 22, diff --git a/lib/pages/routiens/models/routine_details_model.dart b/lib/pages/routiens/models/routine_details_model.dart index 169bd237..8a4a1202 100644 --- a/lib/pages/routiens/models/routine_details_model.dart +++ b/lib/pages/routiens/models/routine_details_model.dart @@ -48,8 +48,7 @@ class RoutineDetailsModel { spaceUuid: spaceUuid, automationName: name, decisionExpr: decisionExpr, - effectiveTime: - effectiveTime ?? EffectiveTime(start: '', end: '', loops: ''), + effectiveTime: effectiveTime ?? EffectiveTime(start: '', end: '', loops: ''), conditions: conditions?.map((c) => c.toCondition()).toList() ?? [], actions: actions.map((a) => a.toAutomationAction()).toList(), ); @@ -64,8 +63,7 @@ class RoutineDetailsModel { if (iconId != null) 'iconUuid': iconId, if (showInDevice != null) 'showInDevice': showInDevice, if (effectiveTime != null) 'effectiveTime': effectiveTime!.toMap(), - if (conditions != null) - 'conditions': conditions!.map((x) => x.toMap()).toList(), + if (conditions != null) 'conditions': conditions!.map((x) => x.toMap()).toList(), if (type != null) 'type': type, if (sceneId != null) 'sceneId': sceneId, if (automationId != null) 'automationId': automationId, @@ -82,12 +80,10 @@ class RoutineDetailsModel { ), iconId: map['iconUuid'], showInDevice: map['showInDevice'], - effectiveTime: map['effectiveTime'] != null - ? EffectiveTime.fromMap(map['effectiveTime']) - : null, + effectiveTime: + map['effectiveTime'] != null ? EffectiveTime.fromMap(map['effectiveTime']) : null, conditions: map['conditions'] != null - ? List.from( - map['conditions'].map((x) => RoutineCondition.fromMap(x))) + ? List.from(map['conditions'].map((x) => RoutineCondition.fromMap(x))) : null, type: map['type'], sceneId: map['sceneId'], @@ -108,15 +104,16 @@ class RoutineAction { final RoutineExecutorProperty? executorProperty; final String productType; final String? type; + final String? icon; - RoutineAction({ - required this.entityId, - required this.actionExecutor, - required this.productType, - this.executorProperty, - this.name, - this.type, - }); + RoutineAction( + {required this.entityId, + required this.actionExecutor, + required this.productType, + this.executorProperty, + this.name, + this.type, + this.icon}); CreateSceneAction toCreateSceneAction() { return CreateSceneAction( @@ -140,22 +137,21 @@ class RoutineAction { 'actionExecutor': actionExecutor, if (type != null) 'type': type, if (name != null) 'name': name, - if (executorProperty != null) - 'executorProperty': executorProperty!.toMap(), + if (executorProperty != null) 'executorProperty': executorProperty!.toMap(), }; } factory RoutineAction.fromMap(Map map) { return RoutineAction( - entityId: map['entityId'] ?? '', - actionExecutor: map['actionExecutor'] ?? '', - productType: map['productType'] ?? '', - name: map['name'] ?? '', - type: map['type'] ?? '', - executorProperty: map['executorProperty'] != null - ? RoutineExecutorProperty.fromMap(map['executorProperty']) - : null, - ); + entityId: map['entityId'] ?? '', + actionExecutor: map['actionExecutor'] ?? '', + productType: map['productType'] ?? '', + name: map['name'] ?? '', + type: map['type'] ?? '', + executorProperty: map['executorProperty'] != null + ? RoutineExecutorProperty.fromMap(map['executorProperty']) + : null, + icon: map['icon']); } } diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index e26d3d12..56961892 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -32,18 +32,17 @@ class DraggableCard extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - final deviceFunctions = - state.selectedFunctions[deviceData['uniqueCustomId']] ?? []; + final deviceFunctions = state.selectedFunctions[deviceData['uniqueCustomId']] ?? []; - int index = state.thenItems.indexWhere( - (item) => item['uniqueCustomId'] == deviceData['uniqueCustomId']); + int index = state.thenItems + .indexWhere((item) => item['uniqueCustomId'] == deviceData['uniqueCustomId']); if (index != -1) { return _buildCardContent(context, deviceFunctions, padding: padding); } - int ifIndex = state.ifItems.indexWhere( - (item) => item['uniqueCustomId'] == deviceData['uniqueCustomId']); + int ifIndex = state.ifItems + .indexWhere((item) => item['uniqueCustomId'] == deviceData['uniqueCustomId']); if (ifIndex != -1) { return _buildCardContent(context, deviceFunctions, padding: padding); @@ -53,8 +52,7 @@ class DraggableCard extends StatelessWidget { data: deviceData, feedback: Transform.rotate( angle: -0.1, - child: - _buildCardContent(context, deviceFunctions, padding: padding), + child: _buildCardContent(context, deviceFunctions, padding: padding), ), childWhenDragging: _buildGreyContainer(), child: _buildCardContent(context, deviceFunctions, padding: padding), @@ -63,8 +61,7 @@ class DraggableCard extends StatelessWidget { ); } - Widget _buildCardContent( - BuildContext context, List deviceFunctions, + Widget _buildCardContent(BuildContext context, List deviceFunctions, {EdgeInsetsGeometry? padding}) { return Stack( children: [ @@ -93,7 +90,7 @@ class DraggableCard extends StatelessWidget { ), ), padding: const EdgeInsets.all(8), - child: deviceData['type'] == 'tap_to_run' + child: deviceData['type'] == 'tap_to_run' || deviceData['type'] == 'scene' ? Image.memory( base64Decode(deviceData['icon']), ) @@ -162,8 +159,7 @@ class DraggableCard extends StatelessWidget { } String _formatFunctionValue(DeviceFunctionData function) { - if (function.functionCode == 'temp_set' || - function.functionCode == 'temp_current') { + if (function.functionCode == 'temp_set' || function.functionCode == 'temp_current') { return '${(function.value / 10).toStringAsFixed(0)}°C'; } else if (function.functionCode.contains('countdown')) { final seconds = function.value.toInt(); From aff7ceeac4a36f522c46289587750b7da5fe4680 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 10:02:20 +0400 Subject: [PATCH 006/175] added condition to check parent name --- .../widgets/dialogs/create_space_dialog.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index e0853e3a..496c1147 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -133,13 +133,7 @@ class CreateSpaceDialogState extends State { isNameFieldInvalid = value.isEmpty; if (!isNameFieldInvalid) { - if ((widget.parentSpace?.children.any( - (child) => child.name == value) ?? - false) || - (widget.parentSpace?.name == value) || - (widget.editSpace?.children.any( - (child) => child.name == value) ?? - false)) { + if (_isNameConflict(value)) { isNameFieldExist = true; isOkButtonEnabled = false; } else { @@ -387,7 +381,13 @@ class CreateSpaceDialogState extends State { ); } - + bool _isNameConflict(String value) { + return (widget.parentSpace?.children.any((child) => child.name == value) ?? + false) || + (widget.editSpace?.parent?.name == value) || + (widget.editSpace?.children.any((child) => child.name == value) ?? + false); + } String _mapIconToProduct(String uuid, List products) { // Find the product with the matching UUID From 4055265b0aa58affa17c5b2015429abf72abd747 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 10:02:57 +0400 Subject: [PATCH 007/175] for create --- .../all_spaces/widgets/dialogs/create_space_dialog.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 496c1147..f6a02833 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -384,6 +384,7 @@ class CreateSpaceDialogState extends State { bool _isNameConflict(String value) { return (widget.parentSpace?.children.any((child) => child.name == value) ?? false) || + (widget.parentSpace?.name == value) || (widget.editSpace?.parent?.name == value) || (widget.editSpace?.children.any((child) => child.name == value) ?? false); From dd5fe10a217bbb32392be3122840d27655ce0652 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 10:03:40 +0400 Subject: [PATCH 008/175] removed print --- .../all_spaces/bloc/space_management_bloc.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index a171f2a7..ffa5687e 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -309,8 +309,6 @@ class SpaceManagementBloc await _api.deleteSpace(communityUuid, parent.uuid!); } } catch (e) { - print( - 'Error deleting space ${parent.name} (UUID: ${parent.uuid}, Community UUID: $communityUuid): $e'); rethrow; // Decide whether to stop execution or continue } } @@ -342,7 +340,6 @@ class SpaceManagementBloc space.uuid = response?.uuid; } } catch (e) { - print('Error creating space ${space.name}: $e'); rethrow; // Stop further execution on failure } } From 4896b6c59372b2920545134916f71045d67064b3 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 10 Dec 2024 09:50:42 +0400 Subject: [PATCH 009/175] changed endpoint for communtiies and spaces, hardcoded p[roject value for now --- lib/services/routines_api.dart | 4 +++- lib/services/space_mana_api.dart | 33 ++++++++++++++++++++--------- lib/utils/constants/api_const.dart | 28 ++++++++++++------------ lib/utils/constants/temp_const.dart | 3 +++ 4 files changed, 43 insertions(+), 25 deletions(-) create mode 100644 lib/utils/constants/temp_const.dart diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index ffc8a8c7..7be3ff11 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -6,6 +6,7 @@ import 'package:syncrow_web/pages/routiens/models/routine_details_model.dart'; import 'package:syncrow_web/pages/routiens/models/routine_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 SceneApi { static final HTTPService _httpService = HTTPService(); @@ -76,7 +77,8 @@ class SceneApi { final response = await _httpService.get( path: ApiEndpoints.getUnitScenes .replaceAll('{spaceUuid}', unitId) - .replaceAll('{communityUuid}', communityId), + .replaceAll('{communityUuid}', communityId) + .replaceAll('{projectId}', TempConst.projectId), queryParameters: {'showInHomePage': showInDevice}, showServerMessage: false, expectedResponseModel: (json) { diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 55875bfa..def94441 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_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 CommunitySpaceManagementApi { // Community Management APIs @@ -15,7 +16,8 @@ class CommunitySpaceManagementApi { while (hasNext) { await HTTPService().get( - path: ApiEndpoints.getCommunityList, + path: ApiEndpoints.getCommunityList + .replaceAll('{projectId}', TempConst.projectId), queryParameters: {'page': page}, expectedResponseModel: (json) { List jsonData = json['data']; @@ -59,7 +61,8 @@ class CommunitySpaceManagementApi { String name, String description) async { try { final response = await HTTPService().post( - path: ApiEndpoints.createCommunity, + path: ApiEndpoints.createCommunity + .replaceAll('{projectId}', TempConst.projectId), body: { 'name': name, 'description': description, @@ -79,7 +82,8 @@ class CommunitySpaceManagementApi { try { final response = await HTTPService().put( path: ApiEndpoints.updateCommunity - .replaceAll('{communityId}', communityId), + .replaceAll('{communityId}', communityId) + .replaceAll('{projectId}', TempConst.projectId), body: { 'name': name, }, @@ -98,7 +102,8 @@ class CommunitySpaceManagementApi { try { final response = await HTTPService().delete( path: ApiEndpoints.deleteCommunity - .replaceAll('{communityId}', communityId), + .replaceAll('{communityId}', communityId) + .replaceAll('{projectId}', TempConst.projectId), expectedResponseModel: (json) { return json['success'] ?? false; }, @@ -113,7 +118,9 @@ class CommunitySpaceManagementApi { Future fetchSpaces(String communityId) async { try { final response = await HTTPService().get( - path: ApiEndpoints.listSpaces.replaceAll('{communityId}', communityId), + path: ApiEndpoints.listSpaces + .replaceAll('{communityId}', communityId) + .replaceAll('{projectId}', TempConst.projectId), expectedResponseModel: (json) { return SpacesResponse.fromJson(json); }, @@ -139,7 +146,8 @@ class CommunitySpaceManagementApi { final response = await HTTPService().get( path: ApiEndpoints.getSpace .replaceAll('{communityId}', communityId) - .replaceAll('{spaceId}', spaceId), + .replaceAll('{spaceId}', spaceId) + .replaceAll('{projectId}', TempConst.projectId), expectedResponseModel: (json) { return SpaceModel.fromJson(json); }, @@ -175,7 +183,9 @@ class CommunitySpaceManagementApi { body['parentUuid'] = parentId; } final response = await HTTPService().post( - path: ApiEndpoints.createSpace.replaceAll('{communityId}', communityId), + path: ApiEndpoints.createSpace + .replaceAll('{communityId}', communityId) + .replaceAll('{projectId}', TempConst.projectId), body: body, expectedResponseModel: (json) { return SpaceModel.fromJson(json['data']); @@ -216,7 +226,8 @@ class CommunitySpaceManagementApi { final response = await HTTPService().put( path: ApiEndpoints.updateSpace .replaceAll('{communityId}', communityId) - .replaceAll('{spaceId}', spaceId), + .replaceAll('{spaceId}', spaceId) + .replaceAll('{projectId}', TempConst.projectId), body: body, expectedResponseModel: (json) { return SpaceModel.fromJson(json['data']); @@ -234,7 +245,8 @@ class CommunitySpaceManagementApi { final response = await HTTPService().delete( path: ApiEndpoints.deleteSpace .replaceAll('{communityId}', communityId) - .replaceAll('{spaceId}', spaceId), + .replaceAll('{spaceId}', spaceId) + .replaceAll('{projectId}', TempConst.projectId), expectedResponseModel: (json) { return json['success'] ?? false; }, @@ -250,7 +262,8 @@ class CommunitySpaceManagementApi { try { final response = await HTTPService().get( path: ApiEndpoints.getSpaceHierarchy - .replaceAll('{communityId}', communityId), + .replaceAll('{communityId}', communityId) + .replaceAll('{projectId}', TempConst.projectId), expectedResponseModel: (json) { final spaceModels = (json['data'] as List) .map((spaceJson) => SpaceModel.fromJson(spaceJson)) diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 007b488d..b8d23259 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -36,21 +36,21 @@ abstract class ApiEndpoints { static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}'; // Space Module - static const String createSpace = '/communities/{communityId}/spaces'; - static const String listSpaces = '/communities/{communityId}/spaces'; - static const String deleteSpace = '/communities/{communityId}/spaces/{spaceId}'; - static const String updateSpace = '/communities/{communityId}/spaces/{spaceId}'; - static const String getSpace = '/communities/{communityId}/spaces/{spaceId}'; - static const String getSpaceHierarchy = '/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'; // Community Module - static const String createCommunity = '/communities'; - static const String getCommunityList = '/communities'; - static const String getCommunityById = '/communities/{communityId}'; - static const String updateCommunity = '/communities/{communityId}'; - static const String deleteCommunity = '/communities/{communityId}'; - static const String getUserCommunities = '/communities/user/{userUuid}'; - static const String createUserCommunity = '/communities/user'; + 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 getDeviceLogsByDate = '/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}'; @@ -68,7 +68,7 @@ abstract class ApiEndpoints { static const String getIconScene = '/scene/icon'; static const String createScene = '/scene/tap-to-run'; static const String createAutomation = '/automation'; - static const String getUnitScenes = '/communities/{communityUuid}/spaces/{spaceUuid}/scenes'; + static const String getUnitScenes = '/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/scenes'; 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}'; diff --git a/lib/utils/constants/temp_const.dart b/lib/utils/constants/temp_const.dart new file mode 100644 index 00000000..e5847b98 --- /dev/null +++ b/lib/utils/constants/temp_const.dart @@ -0,0 +1,3 @@ +class TempConst { + static const projectId = '0e62577c-06fa-41b9-8a92-99a21fbaf51c'; +} From 56d4c34321f0f56f3b988a1f37e5dcc41026f161 Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 12 Dec 2024 19:00:51 +0300 Subject: [PATCH 010/175] roles_and_permission --- assets/icons/active_user.svg | 18 + assets/icons/box_checked.png | Bin 0 -> 754 bytes assets/icons/compleate_process_icon.svg | 9 + assets/icons/current_process_icon.svg | 3 + assets/icons/deactive_user.svg | 18 + assets/icons/empty_box.png | Bin 0 -> 425 bytes assets/icons/invited_icon.svg | 7 + assets/icons/rectangle_check_box.png | Bin 0 -> 523 bytes assets/icons/search_icon_user.svg | 4 + assets/icons/uncompleate_process_icon.svg | 3 + assets/icons/wrong_process_icon.svg | 10 + lib/pages/home/bloc/home_bloc.dart | 9 +- .../bloc/roles_permission_bloc.dart | 42 ++ .../bloc/roles_permission_event.dart | 41 ++ .../bloc/roles_permission_state.dart | 77 +++ .../model/role_model.dart | 6 + .../model/roles_user_model.dart | 22 + .../users_page/bloc/users_bloc.dart | 136 +++++ .../users_page/bloc/users_event.dart | 35 ++ .../users_page/bloc/users_status.dart | 57 ++ .../users_page/view/add_user_dialog.dart | 214 ++++++++ .../users_page/view/basics_view.dart | 279 ++++++++++ .../users_page/view/roles_and_permission.dart | 498 ++++++++++++++++++ .../users_page/view/spaces_access_view.dart | 338 ++++++++++++ .../users_page/view/user_table.dart | 256 +++++++++ .../users_page/view/users_page.dart | 241 +++++++++ .../view/create_role_card.dart | 0 .../roles_and_permission/view/role_card.dart | 57 ++ .../view/roles_and_permission_page.dart | 96 ++++ .../roles_and_permission/view/roles_page.dart | 69 +++ lib/utils/app_routes.dart | 6 +- lib/utils/color_manager.dart | 13 +- lib/utils/constants/assets.dart | 158 ++++-- lib/utils/constants/routes_const.dart | 1 + lib/utils/style.dart | 79 ++- pubspec.lock | 24 +- 36 files changed, 2739 insertions(+), 87 deletions(-) create mode 100644 assets/icons/active_user.svg create mode 100644 assets/icons/box_checked.png create mode 100644 assets/icons/compleate_process_icon.svg create mode 100644 assets/icons/current_process_icon.svg create mode 100644 assets/icons/deactive_user.svg create mode 100644 assets/icons/empty_box.png create mode 100644 assets/icons/invited_icon.svg create mode 100644 assets/icons/rectangle_check_box.png create mode 100644 assets/icons/search_icon_user.svg create mode 100644 assets/icons/uncompleate_process_icon.svg create mode 100644 assets/icons/wrong_process_icon.svg create mode 100644 lib/pages/roles_and_permission/bloc/roles_permission_bloc.dart create mode 100644 lib/pages/roles_and_permission/bloc/roles_permission_event.dart create mode 100644 lib/pages/roles_and_permission/bloc/roles_permission_state.dart create mode 100644 lib/pages/roles_and_permission/model/role_model.dart create mode 100644 lib/pages/roles_and_permission/model/roles_user_model.dart create mode 100644 lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart create mode 100644 lib/pages/roles_and_permission/users_page/bloc/users_event.dart create mode 100644 lib/pages/roles_and_permission/users_page/bloc/users_status.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/basics_view.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/user_table.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/users_page.dart create mode 100644 lib/pages/roles_and_permission/view/create_role_card.dart create mode 100644 lib/pages/roles_and_permission/view/role_card.dart create mode 100644 lib/pages/roles_and_permission/view/roles_and_permission_page.dart create mode 100644 lib/pages/roles_and_permission/view/roles_page.dart diff --git a/assets/icons/active_user.svg b/assets/icons/active_user.svg new file mode 100644 index 00000000..5e0806e0 --- /dev/null +++ b/assets/icons/active_user.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/box_checked.png b/assets/icons/box_checked.png new file mode 100644 index 0000000000000000000000000000000000000000..d93b9d76d9df6c22695ef154f7b27f151dc63a79 GIT binary patch literal 754 zcmV#?`b=x}-yKsn#)Vx;QBaZaH++eUHK6j0|-3R)(Zx zVRaOSP9)Yi6PfZm?+51;vwnwUA1{kLszV?46N#Mg@dwDToF(Sb$}CBbACq`IS;Fa) zz~Mszx>#7^n0n&J>Q83N3p~j@QF&2Fag(Hr4@+8w+u7Iz1}AzfIb-srM+h-Y3qE(N zDt$EXMf#No`2s^H1OpPWNEvtIrWza8YK}~GgPtRgl7^_$$OT)PdQMU=bU+uLe(goD z7f6hn>K1OTW|-G^e(}}=-8d7WSv&AXSE-wPVdpsx&Ga9YczXXYLaJ?rRxwGRO_y+P zK)_IKE4enYBkez8KS0#Zs5fO_#r0AmzsWIY_~BZ7{*>Iabp02{SkjN|Dsv}J#7v~8 zi&m>s+EPri#C2Op?P#P{K5(MBbnSQhSF)*?s?By%w@pjtNR1ApEyXFri~0ApRm@P! zK76)fNrM6R(w1Uk*6`u;_iC45Ky4tColDz_sno@j-PYiZa4T&qcH}qI;PrA^iEH=V zNp~eC*45x7cZxkp5GDeMsxbN};$bw$fpRqDH9$J=5} zF4D<{+9n4l>})L{(!BDgE2ENTj!sbSU0Bi^_DHz6T$NIwhx(K`*iV=()K>+!1AYj4 kSrLMNK@o58iCJanAKq9oum;g}8UO$Q07*qoM6N<$g1q%wMF0Q* literal 0 HcmV?d00001 diff --git a/assets/icons/compleate_process_icon.svg b/assets/icons/compleate_process_icon.svg new file mode 100644 index 00000000..a4159de2 --- /dev/null +++ b/assets/icons/compleate_process_icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/current_process_icon.svg b/assets/icons/current_process_icon.svg new file mode 100644 index 00000000..967928e3 --- /dev/null +++ b/assets/icons/current_process_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/deactive_user.svg b/assets/icons/deactive_user.svg new file mode 100644 index 00000000..7011f5fb --- /dev/null +++ b/assets/icons/deactive_user.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/empty_box.png b/assets/icons/empty_box.png new file mode 100644 index 0000000000000000000000000000000000000000..71e798759d2913e747487d2b5ffe9cbe32b5831f GIT binary patch literal 425 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX1|+Qw)-3{3oCO|{#S9E$svykh8Km+7D9BhG zZ8s34L5V* z+7c~3fABHB^!;!DB8~9lg?8_Yx$F`pq*5c}DlRTyedxRN_uqeqx4jMNsJ&m`KBe4e zS>~m@?aFeOV?{OS3ZJUzxNeel6?|IhSIndsuBl`uQ?PK# zPwm84yY_wX@1ABbA#eNb|3c6BR&^O3w7*x(yh*?BujG-RKY$_2 N;OXk;vd$@?2>^L{tuFuo literal 0 HcmV?d00001 diff --git a/assets/icons/invited_icon.svg b/assets/icons/invited_icon.svg new file mode 100644 index 00000000..5563de14 --- /dev/null +++ b/assets/icons/invited_icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/rectangle_check_box.png b/assets/icons/rectangle_check_box.png new file mode 100644 index 0000000000000000000000000000000000000000..3404c79cabb9ea8574f45e1698a32f58dc98cafc GIT binary patch literal 523 zcmV+m0`&cfP)%D|6&E zzQc@&oE1{6ku-PM&?@{)xeCl}4BK+X%q8mQD5dcp6Lh=(m~ zubS%Fuun6V8UnpXhLQ$pQf$VN?wyj<3mwqH(^p>jM}g#uss6(DX@>as$!9kPbko`h z?Yaf;wo2VpYCp#~`h+WX#je;DyJA=T55$x!y7Bw)yO?5(NGw{=O+0L(o480{3Xjej zsErZ}rs|~LcTUep#!Wl+2+G_Ko|z|NEvxEVa`LwRrHgb@u5y*PPFo9zG;h9nvZ^#o zG(g>($?J_h66OjWDFr>$Ri?jJm^SKG!JWVlLN7%j_yg*=#yzu}^BwrGqlvs0I6VLW N002ovPDHLkV1k%v=!XCR literal 0 HcmV?d00001 diff --git a/assets/icons/search_icon_user.svg b/assets/icons/search_icon_user.svg new file mode 100644 index 00000000..61eca62d --- /dev/null +++ b/assets/icons/search_icon_user.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/uncompleate_process_icon.svg b/assets/icons/uncompleate_process_icon.svg new file mode 100644 index 00000000..4ede6757 --- /dev/null +++ b/assets/icons/uncompleate_process_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/wrong_process_icon.svg b/assets/icons/wrong_process_icon.svg new file mode 100644 index 00000000..de5b475c --- /dev/null +++ b/assets/icons/wrong_process_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index c837e40a..04c35295 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -42,7 +42,8 @@ class HomeBloc extends Bloc { Future _fetchUserInfo(FetchUserInfo event, Emitter emit) async { try { - var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey); + var uuid = + await const FlutterSecureStorage().read(key: UserModel.userUuidKey); user = await HomeApi().fetchUserInfo(uuid); emit(HomeInitial()); } catch (e) { @@ -93,8 +94,10 @@ class HomeBloc extends Bloc { HomeItemModel( title: 'Move in', icon: Assets.moveinIcon, - active: false, - onPress: (context) {}, + active: true, + onPress: (context) { + context.go(RoutesConst.rolesAndPermissions); + }, color: ColorsManager.primaryColor, ), HomeItemModel( diff --git a/lib/pages/roles_and_permission/bloc/roles_permission_bloc.dart b/lib/pages/roles_and_permission/bloc/roles_permission_bloc.dart new file mode 100644 index 00000000..4f4988b3 --- /dev/null +++ b/lib/pages/roles_and_permission/bloc/roles_permission_bloc.dart @@ -0,0 +1,42 @@ +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_state.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/role_model.dart'; + +class RolesPermissionBloc + extends Bloc { + RolesPermissionBloc() : super(RolesInitial()) { + on(_getRoles); + on(changeTapSelected); + } + List roleModel = []; + + FutureOr _getRoles( + GetRoles event, Emitter emit) async { + emit(UsersLoadingState()); + try { + roleModel = [ + RoleModel(roleId: '1', roleImage: '', roleName: 'Admin'), + RoleModel(roleId: '2', roleImage: '', roleName: 'Security'), + RoleModel(roleId: '2', roleImage: '', roleName: 'Reception'), + ]; + emit(UsersLoadedState()); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + bool tapSelect = true; + + changeTapSelected( + ChangeTapSelected event, Emitter emit) { + try { + emit(RolesLoadingState()); + tapSelect = event.selected; + emit(ChangeTapStatus(select: !tapSelect)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } +} diff --git a/lib/pages/roles_and_permission/bloc/roles_permission_event.dart b/lib/pages/roles_and_permission/bloc/roles_permission_event.dart new file mode 100644 index 00000000..d5dce346 --- /dev/null +++ b/lib/pages/roles_and_permission/bloc/roles_permission_event.dart @@ -0,0 +1,41 @@ +import 'package:equatable/equatable.dart'; + +sealed class RolesPermissionEvent extends Equatable { + const RolesPermissionEvent(); +} + +class GetRoles extends RolesPermissionEvent { + const GetRoles(); + @override + List get props => []; +} + +class GetBatchStatus extends RolesPermissionEvent { + final List uuids; + const GetBatchStatus(this.uuids); + @override + List get props => [uuids]; +} + +class GetDeviceRecords extends RolesPermissionEvent { + final String uuid; + + const GetDeviceRecords(this.uuid); + @override + List get props => [uuid]; +} + +class GetDeviceAutomationRecords extends RolesPermissionEvent { + final String uuid; + const GetDeviceAutomationRecords(this.uuid); + @override + List get props => [uuid]; +} + +class ChangeTapSelected extends RolesPermissionEvent { + final bool selected; + const ChangeTapSelected(this.selected); + + @override + List get props => [selected]; +} diff --git a/lib/pages/roles_and_permission/bloc/roles_permission_state.dart b/lib/pages/roles_and_permission/bloc/roles_permission_state.dart new file mode 100644 index 00000000..55c2a8cb --- /dev/null +++ b/lib/pages/roles_and_permission/bloc/roles_permission_state.dart @@ -0,0 +1,77 @@ +import 'package:equatable/equatable.dart'; + +sealed class RolesPermissionState extends Equatable { + const RolesPermissionState(); +} + +final class RolesInitial extends RolesPermissionState { + @override + List get props => []; +} + +final class RolesLoadingState extends RolesPermissionState { + @override + List get props => []; +} +final class UsersLoadingState extends RolesPermissionState { + @override + List get props => []; +} + +final class RolesLoadedState extends RolesPermissionState { + @override + List get props => []; +} +final class UsersLoadedState extends RolesPermissionState { + @override + List get props => []; +} + +final class ErrorState extends RolesPermissionState { + final String message; + + const ErrorState(this.message); + + @override + List get props => [message]; +} + +/// report state +final class SosReportLoadingState extends RolesPermissionState { + @override + List get props => []; +} + +final class RolesErrorState extends RolesPermissionState { + final String message; + + const RolesErrorState(this.message); + + @override + List get props => [message]; +} + +/// automation reports + +final class SosAutomationReportLoadingState extends RolesPermissionState { + @override + List get props => []; +} + +final class SosAutomationReportErrorState extends RolesPermissionState { + final String message; + + const SosAutomationReportErrorState(this.message); + + @override + List get props => [message]; +} + +final class ChangeTapStatus extends RolesPermissionState { + bool select = true; + + ChangeTapStatus({required this.select}); + + @override + List get props => [select]; +} diff --git a/lib/pages/roles_and_permission/model/role_model.dart b/lib/pages/roles_and_permission/model/role_model.dart new file mode 100644 index 00000000..3d139904 --- /dev/null +++ b/lib/pages/roles_and_permission/model/role_model.dart @@ -0,0 +1,6 @@ +class RoleModel { + String? roleId; + String? roleName; + String? roleImage; + RoleModel({this.roleId, this.roleName, this.roleImage}); +} diff --git a/lib/pages/roles_and_permission/model/roles_user_model.dart b/lib/pages/roles_and_permission/model/roles_user_model.dart new file mode 100644 index 00000000..244c58de --- /dev/null +++ b/lib/pages/roles_and_permission/model/roles_user_model.dart @@ -0,0 +1,22 @@ +class RolesUserModel { + String? id; + String? userName; + String? userEmail; + String? userRole; + String? creationDate; + String? creationTime; + String? createdBy; + String? status; + String? action; + RolesUserModel( + {this.id, + this.userName, + this.userEmail, + this.userRole, + this.creationDate, + this.creationTime, + this.status, + this.action, + this.createdBy, + }); +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart new file mode 100644 index 00000000..5de74e4b --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -0,0 +1,136 @@ +import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; + +class UsersBloc extends Bloc { + UsersBloc() : super(UsersInitial()) { + on(_getUsers); + on(_changeUserStatus); + on(isCompleteBasicsFun); + } + + List users = []; + + Future _getUsers(GetUsers event, Emitter emit) async { + emit(UsersLoadingState()); + try { + users = [ + RolesUserModel( + id: '1', + userName: 'user 1', + userEmail: 'test1@test.com', + action: '', + createdBy: 'Admin', + creationDate: '25/10/2024', + creationTime: '10:30 AM', + status: 'Invited', + ), + RolesUserModel( + id: '2', + userName: 'user 2', + userEmail: 'test2@test.com', + action: '', + createdBy: 'Admin', + creationDate: '25/10/2024', + creationTime: '10:30 AM', + status: 'Active', + ), + RolesUserModel( + id: '3', + userName: 'user 3', + userEmail: 'test3@test.com', + action: '', + createdBy: 'Admin', + creationDate: '25/10/2024', + creationTime: '10:30 AM', + status: 'Disabled', + ), + ]; + emit(UsersLoadedState(users: users)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + void _changeUserStatus(ChangeUserStatus event, Emitter emit) { + try { + // Update the user's status + users = users.map((user) { + if (user.id == event.userId) { + return RolesUserModel( + id: user.id, + userName: user.userName, + userEmail: user.userEmail, + createdBy: user.createdBy, + creationDate: user.creationDate, + creationTime: user.creationTime, + status: event.newStatus, + action: user.action, + ); + } + return user; + }).toList(); + + emit(UsersLoadedState(users: users)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + final formKey = GlobalKey(); + final TextEditingController firstNameController = TextEditingController(); + final TextEditingController lastNameController = TextEditingController(); + final TextEditingController emailController = TextEditingController(); + final TextEditingController phoneController = TextEditingController(); + final TextEditingController jobTitleController = TextEditingController(); + + bool isCompleteBasics = false; + bool isCompleteRolePermissions = false; + bool isCompleteSpaces = false; + + int numberBasics = 0; + int numberSpaces = 0; + int numberRole = 0; + + isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { + emit(UsersLoadingState()); + isCompleteBasics = firstNameController.text.isNotEmpty && + lastNameController.text.isNotEmpty && + emailController.text.isNotEmpty && + phoneController.text.isNotEmpty && + jobTitleController.text.isNotEmpty; + emit(ChangeStatusSteps()); + } + + // void checkStatus(CheckStepStatus event, Emitter emit) { + // try { + // // Check if basic fields are completed + // isCompleteBasics = firstNameController.text.isNotEmpty && + // lastNameController.text.isNotEmpty && + // emailController.text.isNotEmpty && + // phoneController.text.isNotEmpty && + // jobTitleController.text.isNotEmpty; + // // Emit the updated state + // if (isCompleteBasics && isCompleteRolePermissions && isCompleteSpaces) { + // } else { + // // emit(IncompleteState( + // // isCompleteBasics, isCompleteRolePermissions, isCompleteSpaces)); + // } + // } catch (e) { + // emit(ErrorState(e.toString())); + // } + // } + +// Example placeholder methods for role permissions and spaces + bool checkRolePermissions() { + // Add logic to check if role permissions are completed + return true; // Replace with actual logic + } + + bool checkSpaces() { + // Add logic to check if spaces are completed + return true; // Replace with actual logic + } +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart new file mode 100644 index 00000000..3b679a7e --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart @@ -0,0 +1,35 @@ +import 'package:equatable/equatable.dart'; + +sealed class UsersEvent extends Equatable { + const UsersEvent(); +} + +class GetUsers extends UsersEvent { + const GetUsers(); + @override + List get props => []; +} + +class GetBatchStatus extends UsersEvent { + final List uuids; + const GetBatchStatus(this.uuids); + @override + List get props => [uuids]; +} + +class ChangeUserStatus extends UsersEvent { + final String userId; + final String newStatus; + + const ChangeUserStatus({required this.userId, required this.newStatus}); + + @override + List get props => [userId, newStatus]; +} + +class CheckStepStatus extends UsersEvent { + final int? steps; + const CheckStepStatus({this.steps}); + @override + List get props => [steps]; +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart new file mode 100644 index 00000000..b9937f77 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart @@ -0,0 +1,57 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; + +sealed class UsersState extends Equatable { + const UsersState(); +} + +final class UsersInitial extends UsersState { + @override + List get props => []; +} + +final class ChangeStatusSteps extends UsersState { + @override + List get props => []; +} + +final class UsersLoadingState extends UsersState { + @override + List get props => []; +} + +final class UsersLoadedState extends UsersState { + List users = []; + UsersLoadedState({required this.users}); + @override + List get props => [users]; +} + +final class ErrorState extends UsersState { + final String message; + + const ErrorState(this.message); + + @override + List get props => [message]; +} + +final class RolesErrorState extends UsersState { + final String message; + + const RolesErrorState(this.message); + + @override + List get props => [message]; +} + +/// automation reports + +final class ChangeTapStatus extends UsersState { + bool select = true; + + ChangeTapStatus({required this.select}); + + @override + List get props => [select]; +} diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart new file mode 100644 index 00000000..b2ee8cf5 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart @@ -0,0 +1,214 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/view/basics_view.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/view/spaces_access_view.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class AddNewUserDialog extends StatefulWidget { + const AddNewUserDialog({super.key}); + + @override + _AddNewUserDialogState createState() => _AddNewUserDialogState(); +} + +class _AddNewUserDialogState extends State { + int currentStep = 1; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => UsersBloc(), + child: BlocConsumer( + listener: (context, state) {}, + builder: (context, state) { + final _blocRole = BlocProvider.of(context); + + return Dialog( + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(20))), + width: 900, + child: Column( + children: [ + // Title + const Padding( + padding: EdgeInsets.all(8.0), + child: SizedBox( + child: Text( + "Add New User", + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold), + ), + ), + ), + const Divider(), + Expanded( + child: Row( + children: [ + // Sidebar for Steps + Expanded( + child: Container( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildStepIndicator(1, "Basics", _blocRole), + _buildStepIndicator(2, "Spaces", _blocRole), + _buildStepIndicator( + 3, "Role & Permissions", _blocRole), + ], + ), + ), + ), + Container( + width: 1, + color: ColorsManager.grayBorder, + ), + // Main content (Form) + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + Expanded( + child: _getFormContent(), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text("Cancel"), + ), + ElevatedButton( + onPressed: () { + if (_blocRole.formKey.currentState + ?.validate() ?? + false) { + // Proceed to next step or finish + setState(() { + if (currentStep < 3) { + currentStep++; + } else { + Navigator.of(context).pop(); + } + }); + } + }, + child: Text(currentStep < 3 + ? "Next" + : "Finish"), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ), + ], + ), + )); + })); + } + + // Method to get the form content based on the current step + Widget _getFormContent() { + switch (currentStep) { + case 1: + return const BasicsView(); + case 2: + return const SpacesAccessView(); + case 3: + return const RolesAndPermission(); + default: + return Container(); + } + } + + // Helper method to build step indicators + Widget _buildStepIndicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + // if (bloc.formKey.currentState?.validate() ?? false) { + setState(() { + currentStep = step; + if (currentStep == 1) { + bloc.numberBasics = 1; + } else if (currentStep == 2) { + bloc.numberSpaces = 2; + } else if (currentStep == 3) { + bloc.numberRole = 3; + } + }); + // } + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + SvgPicture.asset( + currentStep == step + ? Assets.currentProcessIcon + : bloc.isCompleteBasics == false + && (bloc.numberBasics != 0 || + bloc.numberRole != 0 || + bloc.numberSpaces != 0) + ? Assets.wrongProcessIcon + : Assets.uncomplete_ProcessIcon, + + width: 25, + height: 25, + ), + const SizedBox(width: 10), + Text( + label, + style: TextStyle( + fontSize: 16, + color: currentStep == step + ? ColorsManager.blackColor + : ColorsManager.greyColor, + fontWeight: currentStep == step + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ], + ), + ), + if (step != 3) + Padding( + padding: const EdgeInsets.all(5.0), + child: Padding( + padding: const EdgeInsets.only(left: 12), + child: Container( + height: 60, + width: 1, + color: Colors.grey, + ), + ), + ) + ], + ), + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/view/basics_view.dart new file mode 100644 index 00000000..d64d837d --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/basics_view.dart @@ -0,0 +1,279 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class BasicsView extends StatelessWidget { + const BasicsView({super.key}); + @override + Widget build(BuildContext context) { + return BlocBuilder(builder: (context, state) { + final _blocRole = BlocProvider.of(context); + return Form( + key: _blocRole.formKey, + child: ListView( + shrinkWrap: true, + children: [ + Text( + 'Set up the basics', + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 20, + color: Colors.black), + ), + const SizedBox( + height: 80, + ), + Text( + 'To get started, fill out some basic information about who you’re adding as a user.', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 14, + ), + ), + const SizedBox( + height: 25, + ), + Row( + children: [ + // First Name + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text( + 'First Name', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + style: TextStyle(color: Colors.black), + controller: _blocRole.firstNameController, + decoration: inputTextFormDeco( + hintText: "Enter first name", + ).copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Enter first name'; + } + return null; + }, + ), + ), + ], + ), + ), + + SizedBox(width: 10), + + // Last Name + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + const Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text('Last Name', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + )), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _blocRole.lastNameController, + style: TextStyle(color: Colors.black), + decoration: + inputTextFormDeco(hintText: "Enter last name") + .copyWith( + hintStyle: context + .textTheme.bodyMedium + ?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray)), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Enter last name'; + } + return null; + }, + ), + ), + ], + ), + ), + ], + ), + SizedBox(height: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text( + 'Email Address', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _blocRole.emailController, + style: TextStyle(color: Colors.black), + decoration: inputTextFormDeco(hintText: "name@example.com") + .copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Enter last name'; + } + return null; + }, + ), + ), + ], + ), + SizedBox(height: 10), + Row( + children: [ + // Phone Number + + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text( + 'Mobile Number', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + style: const TextStyle(color: Colors.black), + controller: _blocRole.phoneController, + decoration: inputTextFormDeco( + hintText: "05x xxx xxxx", + ).copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a phone number'; + } + return null; + }, + keyboardType: TextInputType.phone, + ), + ), + ], + ), + ), + + SizedBox(width: 10), + + // Job Title + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text( + 'Job Title', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _blocRole.jobTitleController, + style: TextStyle(color: Colors.black), + decoration: inputTextFormDeco( + hintText: "Job Title (Optional)") + .copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + keyboardType: TextInputType.phone, + ), + ), + ], + ), + ), + ], + ), + SizedBox(height: 20), + ], + ), + ); + }); + } +} diff --git a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart new file mode 100644 index 00000000..c9b58581 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart @@ -0,0 +1,498 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.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 RolesAndPermission extends StatelessWidget { + const RolesAndPermission({super.key}); + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + + return BlocBuilder(builder: (context, state) { + final _blocRole = BlocProvider.of(context); + return Container( + color: Colors.white, + child: Form( + key: _blocRole.formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Role & Permissions', + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 20, + color: Colors.black), + ), + const SizedBox( + height: 15, + ), + const SizedBox(width: 300, height: 110, child: DropdownExample()), + const SizedBox(height: 10), + Expanded( + child: SizedBox( + child: Column( + children: [ + Expanded( + flex: 2, + child: 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), + controller: _blocRole.firstNameController, + 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), + ), + ), + ), + ), + ], + ), + ), + ), + ), + Expanded( + flex: 7, + child: Container( + color: ColorsManager.CircleRolesBackground, + padding: const EdgeInsets.all(8.0), + child: Container( + color: ColorsManager.whiteColors, + child: const DeviceManagement()))) + ], + ), + ), + ), + ], + ), + ), + ); + }); + } +} + +class DropdownExample extends StatefulWidget { + const DropdownExample({super.key}); + + @override + _DropdownExampleState createState() => _DropdownExampleState(); +} + +class _DropdownExampleState extends State { + String? selectedRole; + List roles = ['Admin', 'User', 'Guest', 'Moderator']; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Role", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: Colors.black, + ), + ), + const SizedBox(height: 8), + SizedBox( + child: DropdownButtonFormField( + alignment: Alignment.center, + focusColor: ColorsManager.whiteColors, + autofocus: true, + value: selectedRole, + items: roles.map((role) { + return DropdownMenuItem( + value: role, + child: Text(role), + ); + }).toList(), + onChanged: (value) { + setState(() { + selectedRole = value; + }); + }, + padding: EdgeInsets.zero, + icon: const SizedBox.shrink(), + borderRadius: const BorderRadius.all(Radius.circular(10)), + hint: const Padding( + padding: EdgeInsets.only(left: 20), + child: Text("Please Select"), + ), + decoration: inputTextFormDeco().copyWith( + contentPadding: EdgeInsets.zero, + suffixIcon: Container( + padding: EdgeInsets.zero, + width: 70, + height: 50, + decoration: BoxDecoration( + color: ColorsManager.graysColor, + borderRadius: const BorderRadius.only( + bottomRight: Radius.circular(10), + topRight: Radius.circular(10), + ), + border: Border.all( + color: ColorsManager.grayBorder, + width: 1.0, + ), + ), + child: const Center( + child: Icon(Icons.keyboard_arrow_down), + ), + ), + ), + ), + ), + ], + ), + ); + } +} + +class DeviceManagement extends StatefulWidget { + const DeviceManagement({Key? key}) : super(key: key); + + @override + _DeviceManagementState createState() => _DeviceManagementState(); +} + +class _DeviceManagementState extends State { + final List options = [ + MainRoleOption( + id: '1', + title: "Device Management", + subOptions: [ + SubRoleOption( + id: '11', + title: "Manage devices in private spaces", + children: [ + ChildRoleOption(id: '111', title: "Control"), + ChildRoleOption(id: '112', title: "Assign device"), + ChildRoleOption(id: '113', title: "View"), + ], + ), + SubRoleOption( + id: '12', + title: "Manage", + children: [ + ChildRoleOption(id: '121', title: "cc"), + ChildRoleOption(id: '122', title: "Assign"), + ChildRoleOption(id: '123', title: "s"), + ], + ), + ], + ), + MainRoleOption( + id: '2', + title: "Device Management", + subOptions: [ + SubRoleOption( + id: '22', + title: "Manage devices in private spaces", + children: [ + ChildRoleOption(id: '211', title: "Control"), + ChildRoleOption(id: '212', title: "Assign device"), + ChildRoleOption(id: '213', title: "View"), + ], + ), + ], + ), + ]; + + void toggleOptionById(String id) { + setState(() { + for (var mainOption in options) { + if (mainOption.id == id) { + final isChecked = + checkifOneOfthemChecked(mainOption) == CheckState.all; + mainOption.isChecked = !isChecked; + + for (var subOption in mainOption.subOptions) { + subOption.isChecked = !isChecked; + for (var child in subOption.children) { + child.isChecked = !isChecked; + } + } + return; + } + + for (var subOption in mainOption.subOptions) { + if (subOption.id == id) { + subOption.isChecked = !subOption.isChecked; + for (var child in subOption.children) { + child.isChecked = subOption.isChecked; + } + mainOption.isChecked = + mainOption.subOptions.every((sub) => sub.isChecked); + return; + } + + for (var child in subOption.children) { + if (child.id == id) { + child.isChecked = !child.isChecked; + subOption.isChecked = + subOption.children.every((child) => child.isChecked); + mainOption.isChecked = + mainOption.subOptions.every((sub) => sub.isChecked); + return; + } + } + } + } + }); + } + + CheckState checkifOneOfthemChecked(MainRoleOption mainOption) { + bool allSelected = true; + bool someSelected = false; + + for (var subOption in mainOption.subOptions) { + if (subOption.isChecked) { + someSelected = true; + } else { + allSelected = false; + } + + for (var child in subOption.children) { + if (child.isChecked) { + someSelected = true; + } else { + allSelected = false; + } + } + } + + if (allSelected) { + return CheckState.all; + } else if (someSelected) { + return CheckState.some; + } else { + return CheckState.none; + } + } + + @override + Widget build(BuildContext context) { + return ListView.builder( + padding: const EdgeInsets.all(8), + itemCount: options.length, + itemBuilder: (context, index) { + final option = options[index]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + InkWell( + onTap: () => toggleOptionById(option.id), + child: Builder( + builder: (context) { + final checkState = checkifOneOfthemChecked(option); + + if (checkState == CheckState.all) { + return Image.asset( + Assets.CheckBoxChecked, + width: 20, + height: 20, + ); + } else if (checkState == CheckState.some) { + return Image.asset( + Assets.rectangleCheckBox, + width: 20, + height: 20, + ); + } else { + return Image.asset( + Assets.emptyBox, + width: 20, + height: 20, + ); + } + }, + ), + ), + const SizedBox(width: 8), + Text( + option.title, + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 12, + color: ColorsManager.blackColor), + ), + ], + ), + const SizedBox( + height: 10, + ), + ...option.subOptions.map((subOption) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + InkWell( + onTap: () => toggleOptionById(subOption.id), + child: Builder( + builder: (context) { + final checkState = + checkifOneOfthemChecked(MainRoleOption( + id: subOption.id, + title: subOption.title, + subOptions: [subOption], + )); + + if (checkState == CheckState.all) { + return Image.asset( + Assets.CheckBoxChecked, + width: 20, + height: 20, + ); + } else if (checkState == CheckState.some) { + return Image.asset( + Assets.rectangleCheckBox, + width: 20, + height: 20, + ); + } else { + return Image.asset( + Assets.emptyBox, + width: 20, + height: 20, + ); + } + }, + ), + ), + const SizedBox(width: 8), + Text( + subOption.title, + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 12, + color: ColorsManager.lightGreyColor), + ), + ], + ), + Padding( + padding: const EdgeInsets.only(left: 50.0), + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, // 2 items per row + mainAxisSpacing: 2.0, // Space between rows + crossAxisSpacing: 0.2, // Space between columns + childAspectRatio: 5, // Adjust aspect ratio as needed + ), + itemCount: subOption.children.length, + itemBuilder: (context, index) { + final child = subOption.children[index]; + return CheckboxListTile( + dense: true, + controlAffinity: ListTileControlAffinity.leading, + title: Text( + child.title, + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.lightGreyColor), + ), + value: child.isChecked, + onChanged: (value) => toggleOptionById(child.id), + ); + }, + ), + ) + ], + ); + }).toList(), + ], + ); + }, + ); + } +} + +class MainRoleOption { + String id; + String title; + bool isChecked; + List subOptions; + MainRoleOption({ + required this.id, + required this.title, + this.isChecked = false, + this.subOptions = const [], + }); +} + +class SubRoleOption { + String id; + String title; + bool isChecked; + List children; + + SubRoleOption({ + required this.id, + required this.title, + this.isChecked = false, + this.children = const [], + }); +} + +class ChildRoleOption { + String id; + String title; + bool isChecked; + + ChildRoleOption({ + required this.id, + required this.title, + this.isChecked = false, + }); +} + +enum CheckState { none, some, all } diff --git a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart new file mode 100644 index 00000000..9dc35a89 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart @@ -0,0 +1,338 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.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 SpacesAccessView extends StatelessWidget { + const SpacesAccessView({super.key}); + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + + return BlocBuilder(builder: (context, state) { + final _blocRole = BlocProvider.of(context); + return Container( + color: Colors.white, + child: Form( + key: _blocRole.formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Spaces access', + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 20, + color: Colors.black), + ), + const SizedBox( + height: 35, + ), + const SizedBox( + child: Text( + 'Select the spaces you would like to grant access to for the user you are adding'), + ), + const SizedBox( + height: 25, + ), + Expanded( + child: SizedBox( + child: Column( + children: [ + Expanded( + flex: 2, + child: 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), + controller: _blocRole.firstNameController, + 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), + ), + ), + ), + ), + ], + ), + ), + ), + ), + Expanded( + flex: 7, + child: Container( + color: ColorsManager.CircleRolesBackground, + padding: const EdgeInsets.all(8.0), + child: Container( + color: ColorsManager.whiteColors, + child: TreeView()))) + ], + ), + ), + ), + ], + ), + ), + ); + }); + } +} + +class TreeNode { + String title; + bool isChecked; + bool isHighlighted; + bool isExpanded; // New property to manage expansion + List children; + + TreeNode({ + required this.title, + this.isChecked = false, + this.isHighlighted = false, + this.isExpanded = false, // Default to collapsed + this.children = const [], + }); +} + +class TreeView extends StatefulWidget { + @override + _TreeViewState createState() => _TreeViewState(); +} + +class _TreeViewState extends State { + List treeData = [ + TreeNode( + title: 'Downtown Dubai', + ), + TreeNode(title: 'Dubai Creek Harbour', isHighlighted: true), + TreeNode( + title: 'Dubai Hills Estate', + isHighlighted: true, + children: [ + TreeNode(title: 'North Side'), + TreeNode( + title: 'South Side', + isHighlighted: true, + children: [ + TreeNode(title: 'Hills Business Park'), + TreeNode(title: 'Park Point'), + TreeNode(title: 'Acacia'), + TreeNode( + title: 'Executive Residence', + children: [ + TreeNode(title: 'Residence I'), + TreeNode( + title: 'Residence II', + children: [ + TreeNode(title: 'Ground Floor', isHighlighted: true), + TreeNode(title: '1st Floor', isHighlighted: true), + TreeNode(title: 'Pool', isHighlighted: true), + TreeNode(title: 'Gym', isHighlighted: true), + ], + ), + ], + ), + ], + ), + TreeNode( + title: 'South Side', + isHighlighted: true, + children: [ + TreeNode(title: 'Hills Business Park'), + TreeNode(title: 'Park Point'), + TreeNode(title: 'Acacia'), + TreeNode( + title: 'Executive Residence', + children: [ + TreeNode(title: 'Residence I'), + TreeNode( + title: 'Residence II', + children: [ + TreeNode(title: 'Ground Floor', isHighlighted: true), + TreeNode(title: '1st Floor', isHighlighted: true), + TreeNode(title: 'Pool', isHighlighted: true), + TreeNode(title: 'Gym', isHighlighted: true), + ], + ), + ], + ), + ], + ), + ], + ), + ]; + + Widget _buildTree(List nodes, {int level = 0}) { + return Column( + children: nodes.map((node) => _buildNode(node, level: level)).toList(), + ); + } + + Widget _buildNode(TreeNode node, {int level = 0}) { + return Container( + color: node.isHighlighted ? Colors.blue.shade100 : Colors.transparent, + child: Padding( + padding: EdgeInsets.only(left: level * 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + GestureDetector( + onTap: () { + setState(() { + node.isExpanded = !node.isExpanded; // Toggle expansion + }); + }, + child: Icon( + node.children.isNotEmpty + ? (node.isExpanded + ? Icons.arrow_drop_down + : Icons.arrow_right) + : Icons.arrow_right, + color: node.children.isNotEmpty + ? Colors.black + : Colors.transparent, + ), + ), + GestureDetector( + onTap: () { + setState(() { + node.isChecked = !node.isChecked; + _updateChildrenCheckStatus(node, node.isChecked); + _updateParentCheckStatus(node); + }); + }, + child: Image.asset( + _getCheckBoxImage(node), + width: 20, + height: 20, + ), + ), + Expanded( + child: Text( + node.title, + style: TextStyle( + fontSize: 16, + color: Colors.black87, + ), + ), + ), + ], + ), + if (node.isExpanded && node.children.isNotEmpty) + Padding( + padding: const EdgeInsets.only(left: 24.0), + child: _buildTree(node.children, level: level + 1), + ), + ], + ), + ), + ); + } + + // Determine the appropriate image based on the check state + String _getCheckBoxImage(TreeNode node) { + if (node.children.isEmpty) { + return node.isChecked ? Assets.CheckBoxChecked : Assets.emptyBox; + } + if (_areAllChildrenChecked(node)) { + return Assets.CheckBoxChecked; + } else if (_areSomeChildrenChecked(node)) { + return Assets.rectangleCheckBox; + } else { + return Assets.emptyBox; + } + } + + // Helper to determine if all children are checked + bool _areAllChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.every((child) => + child.isChecked && + (child.children.isEmpty || _areAllChildrenChecked(child))); + } + + // Helper to determine if some children are checked + bool _areSomeChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.any((child) => + child.isChecked || + (child.children.isNotEmpty && _areSomeChildrenChecked(child))); + } + + // Update the checkbox state for all children + void _updateChildrenCheckStatus(TreeNode node, bool isChecked) { + for (var child in node.children) { + child.isChecked = isChecked; + _updateChildrenCheckStatus(child, isChecked); + } + } + + // Update the checkbox state for parent nodes + void _updateParentCheckStatus(TreeNode node) { + TreeNode? parent = _findParent(treeData, node); + if (parent != null) { + setState(() { + parent.isChecked = _areAllChildrenChecked(parent); + _updateParentCheckStatus(parent); // Recursively update ancestors + }); + } + } + + // Helper to find a node's parent + TreeNode? _findParent(List nodes, TreeNode target) { + for (var node in nodes) { + if (node.children.contains(target)) { + return node; + } + var parent = _findParent(node.children, target); + if (parent != null) return parent; + } + return null; + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: _buildTree(treeData), + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/view/user_table.dart b/lib/pages/roles_and_permission/users_page/view/user_table.dart new file mode 100644 index 00000000..f951f06e --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/user_table.dart @@ -0,0 +1,256 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DynamicTableScreen extends StatefulWidget { + final List titles; + final List> rows; + + DynamicTableScreen({required this.titles, required this.rows}); + + @override + _DynamicTableScreenState createState() => _DynamicTableScreenState(); +} + +class _DynamicTableScreenState extends State + with WidgetsBindingObserver { + late List columnWidths; + + // @override + // void initState() { + // super.initState(); + // // Initialize column widths with default sizes proportional to the screen width + // // Assigning placeholder values here. The actual sizes will be updated in `build`. + // } + @override + void initState() { + super.initState(); + columnWidths = List.filled(widget.titles.length, 150.0); + + WidgetsBinding.instance.addObserver(this); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeMetrics() { + super.didChangeMetrics(); + // Screen size might have changed + final newScreenWidth = MediaQuery.of(context).size.width; + setState(() { + columnWidths = List.generate(widget.titles.length, (index) { + if (index == 1) { + return newScreenWidth * + 0.12; // 20% of screen width for the second column + } else if (index == 9) { + return newScreenWidth * + 0.2; // 25% of screen width for the tenth column + } + return newScreenWidth * + 0.09; // Default to 10% of screen width for other columns + }); + }); + } + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + // Initialize column widths if they are still set to placeholder values + if (columnWidths.every((width) => width == 150.0)) { + columnWidths = List.generate(widget.titles.length, (index) { + if (index == 1) { + return screenWidth * + 0.12; // 20% of screen width for the second column + } else if (index == 9) { + return screenWidth * 0.2; // 25% of screen width for the tenth column + } + return screenWidth * + 0.09; // Default to 10% of screen width for other columns + }); + setState(() {}); + } + return SizedBox( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: FittedBox( + child: Column( + children: [ + // Header Row with Resizable Columns + Container( + color: ColorsManager.CircleRolesBackground, + child: Row( + children: List.generate(widget.titles.length, (index) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + FittedBox( + child: Container( + padding: const EdgeInsets.only(left: 10, right: 10), + width: columnWidths[index], + child: Text( + widget.titles[index], + style: const TextStyle( + fontWeight: FontWeight.w400, + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), + ), + ), + GestureDetector( + onHorizontalDragUpdate: (details) { + setState(() { + columnWidths[index] = (columnWidths[index] + + details.delta.dx) + .clamp( + 150.0, 300.0); // Minimum & Maximum size + }); + }, + child: MouseRegion( + cursor: SystemMouseCursors + .resizeColumn, // Set the cursor to resize + child: Container( + color: Colors.green, + child: Container( + color: ColorsManager.boxDivider, + width: 1, + height: 50, // Height of the header cell + ), + ), + ), + ), + ], + ); + }), + ), + ), + // Data Rows with Dividers + Container( + color: ColorsManager.whiteColors, + child: Column( + children: widget.rows.map((row) { + int rowIndex = widget.rows.indexOf(row); + return Column( + children: [ + Container( + child: Padding( + padding: const EdgeInsets.only( + left: 5, top: 10, right: 5, bottom: 10), + child: Row( + children: List.generate(row.length, (index) { + return SizedBox( + width: columnWidths[index], + child: SizedBox( + child: Padding( + padding: const EdgeInsets.only( + left: 15, right: 10), + child: row[index], + ), + ), + ); + }), + ), + ), + ), + if (rowIndex < widget.rows.length - 1) + Row( + children: + List.generate(widget.titles.length, (index) { + return SizedBox( + width: columnWidths[index], + child: const Divider( + color: ColorsManager.boxDivider, + thickness: 1, + height: 1, + ), + ); + })) + // Add a Divider below each row except the last one + ], + ); + }).toList(), + ), + ), + ], + ), + ), + ), + ); + } +} + + + + // Widget build(BuildContext context) { + // return Scaffold( + // body: SingleChildScrollView( + // scrollDirection: Axis.horizontal, + // child: SingleChildScrollView( + // scrollDirection: Axis.vertical, + // child: Column( + // children: [ + // // Header Row with Resizable Columns + // Container( + // color: Colors.green, + // child: Row( + // children: List.generate(widget.titles.length, (index) { + // return Row( + // children: [ + // Container( + // width: columnWidths[index], + // decoration: const BoxDecoration( + // color: Colors.green, + // ), + // child: Text( + // widget.titles[index], + // style: TextStyle(fontWeight: FontWeight.bold), + // textAlign: TextAlign.center, + // ), + // ), + // GestureDetector( + // onHorizontalDragUpdate: (details) { + // setState(() { + // columnWidths[index] = (columnWidths[index] + + // details.delta.dx) + // .clamp(50.0, 300.0); // Minimum & Maximum size + // }); + // }, + // child: MouseRegion( + // cursor: SystemMouseCursors + // .resizeColumn, // Set the cursor to resize + // child: Container( + // color: Colors.green, + // child: Container( + // color: Colors.black, + // width: 1, + // height: 50, // Height of the header cell + // ), + // ), + // ), + // ), + // ], + // ); + // }), + // ), + // ), + // // Data Rows + // ...widget.rows.map((row) { + // return Row( + // children: List.generate(row.length, (index) { + // return Container( + // width: columnWidths[index], + // child: row[index], + // ); + // }), + // ); + // }).toList(), + // ], + // ), + // ), + // ), + // ); + // } \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/view/users_page.dart b/lib/pages/roles_and_permission/users_page/view/users_page.dart new file mode 100644 index 00000000..273b8a34 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/users_page.dart @@ -0,0 +1,241 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/view/add_user_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/view/user_table.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 UsersPage extends StatelessWidget { + const UsersPage({super.key}); + @override + Widget build(BuildContext context) { + final TextEditingController searchController = TextEditingController(); + + Widget actionButton({required String title, required Function()? onTap}) { + return InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.only(left: 8, right: 8), + child: Text( + title, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: title == "Delete" + ? ColorsManager.red + : ColorsManager.spaceColor, + fontWeight: FontWeight.w400, + ), + ), + ), + ); + } + + Widget status({required String status}) { + return Center( + child: Container( + padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(20)), + color: status == "Invited" + ? ColorsManager.invitedOrange.withOpacity(0.5) + : status == "Active" + ? ColorsManager.activeGreen.withOpacity(0.5) + : ColorsManager.disabledPink.withOpacity(0.5), + ), + child: Padding( + padding: + const EdgeInsets.only(left: 10, right: 10, bottom: 5, top: 5), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + status, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: status == "Invited" + ? ColorsManager.invitedOrangeText + : status == "Active" + ? ColorsManager.activeGreenText + : ColorsManager.disabledRedText, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ), + ); + } + + Widget changeIconStatus( + {required String userId, + required String status, + required Function()? onTap}) { + return Center( + child: InkWell( + onTap: () { + final newStatus = status == 'Active' + ? 'Disabled' + : status == 'Disabled' + ? 'Invited' + : 'Active'; + context + .read() + .add(ChangeUserStatus(userId: userId, newStatus: newStatus)); + }, + child: Padding( + padding: + const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), + child: SvgPicture.asset( + status == "Invited" + ? Assets.invitedIcon + : status == "Active" + ? Assets.activeUser + : Assets.deActiveUser, + height: 35, + ), + ), + ), + ); + } + +// return RolesAndPermission(); +// } +// } + return BlocBuilder( + builder: (context, state) { + final screenSize = MediaQuery.of(context).size; + + if (state is UsersLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is UsersLoadedState) { + return Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + decoration: containerDecoration.copyWith( + borderRadius: const BorderRadius.all( + Radius.circular(20), + ), + ), + width: screenSize.width * 0.4, + child: TextFormField( + controller: searchController, + style: const TextStyle(color: Colors.black), + decoration: textBoxDecoration(radios: 15)!.copyWith( + fillColor: ColorsManager.whiteColors, + errorStyle: const TextStyle(height: 0), + hintStyle: context.textTheme.titleSmall?.copyWith( + color: Colors.grey, + fontSize: 12, + ), + hintText: 'Search', + suffixIcon: SizedBox( + child: SvgPicture.asset( + Assets.searchIconUser, + fit: BoxFit.none, + ), + ), + ), + ), + ), + const SizedBox(width: 20), + InkWell( + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return const AddNewUserDialog(); + }, + ).then((listDevice) { + if (listDevice != null) {} + }); + }, + child: Container( + decoration: containerWhiteDecoration, + width: screenSize.width * 0.18, + height: 50, + child: const Center( + child: Text( + 'Add New User', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: ColorsManager.blueColor, + ), + ), + ), + ), + ), + ], + ), + const SizedBox(height: 25), + DynamicTableScreen( + titles: const [ + "Full Name", + "Email Address", + "Job Title", + "Role", + "Creation Date", + "Creation Time", + "Created By", + "Status", + "De/Activate", + "Action" + ], + rows: state.users.map((user) { + return [ + Text(user.userName!), + Text(user.userEmail!), + const Text("Test"), + const Text("Member"), + Text(user.creationDate!), + Text(user.creationTime!), + Text(user.createdBy!), + changeIconStatus( + status: user.status!, + userId: user.id!, + onTap: () {}, + ), + status(status: user.status!), + Row( + children: [ + actionButton( + title: "Activity Log", + onTap: () {}, + ), + actionButton( + title: "Edit", + onTap: () {}, + ), + actionButton( + title: "Delete", + onTap: () {}, + ), + ], + ), + ]; + }).toList(), + ), + ], + ), + ); + } else if (state is ErrorState) { + return Center(child: Text(state.message)); + } else { + return const Center(child: Text('No data available.')); + } + }, + ); + } +} diff --git a/lib/pages/roles_and_permission/view/create_role_card.dart b/lib/pages/roles_and_permission/view/create_role_card.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/pages/roles_and_permission/view/role_card.dart b/lib/pages/roles_and_permission/view/role_card.dart new file mode 100644 index 00000000..b3f59ee9 --- /dev/null +++ b/lib/pages/roles_and_permission/view/role_card.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class RoleCard extends StatelessWidget { + final String name; + const RoleCard({super.key, required this.name}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: ColorsManager.whiteColors, // Card background color + borderRadius: BorderRadius.circular(20), // Rounded corners + boxShadow: [ + BoxShadow( + color: ColorsManager.blackColor.withOpacity(0.2), // Shadow color + blurRadius: 20, // Spread of the shadow + offset: const Offset(2, 2), // No directional bias + spreadRadius: 1, // Ensures the shadow is more noticeable + ), + ], + ), + child: Container( + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(20), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const CircleAvatar( + backgroundColor: ColorsManager.neutralGray, + radius: 65, + child: CircleAvatar( + backgroundColor: ColorsManager.CircleRolesBackground, + radius: 62, + child: Icon( + Icons.admin_panel_settings, + size: 40, + color: Colors.blue, + ), + ), + ), + Text( + name, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart new file mode 100644 index 00000000..05f924ca --- /dev/null +++ b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; +import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_state.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/view/users_page.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/web_layout/web_scaffold.dart'; + +class RolesAndPermissionPage extends StatelessWidget { + const RolesAndPermissionPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => + RolesPermissionBloc()..add(const GetRoles()), + child: BlocConsumer( + listener: (context, state) {}, + builder: (context, state) { + final _blocRole = BlocProvider.of(context); + + return state is RolesLoadingState + ? const Center(child: CircularProgressIndicator()) + : WebScaffold( + enableMenuSidebar: false, + appBarTitle: FittedBox( + child: Text( + 'Roles & Permissions', + style: Theme.of(context).textTheme.headlineLarge, + ), + ), + rightBody: const NavigateHomeGridView(), + centerBody: Row( + children: [ + // TextButton( + // style: TextButton.styleFrom( + // backgroundColor: null, + // ), + // onPressed: () { + // _blocRole.add(const ChangeTapSelected(true)); + // }, + // child: Text( + // 'Roles', + // style: context.textTheme.titleMedium?.copyWith( + // color: (_blocRole.tapSelect == true) + // ? ColorsManager.whiteColors + // : ColorsManager.grayColor, + // fontWeight: (_blocRole.tapSelect == true) + // ? FontWeight.w700 + // : FontWeight.w400, + // ), + // ), + // ), + TextButton( + style: TextButton.styleFrom( + backgroundColor: null, + ), + onPressed: () { + // _blocRole.add(const ChangeTapSelected(false)); + }, + child: Text( + 'Users', + style: context.textTheme.titleMedium?.copyWith( + color: (_blocRole.tapSelect == true) + ? ColorsManager.whiteColors + : ColorsManager.grayColor, + fontWeight: (_blocRole.tapSelect == true) + ? FontWeight.w700 + : FontWeight.w400, + ), + ), + ), + ], + ), + scaffoldBody: BlocProvider( + create: (context) => UsersBloc()..add(const GetUsers()), + child: const UsersPage(), + ) + // _blocRole.tapSelect == false + // ? UsersPage( + // blocRole: _blocRole, + // ) + // : RolesPage( + // blocRole: _blocRole, + // ) + ); + }, + ), + ); + } +} diff --git a/lib/pages/roles_and_permission/view/roles_page.dart b/lib/pages/roles_and_permission/view/roles_page.dart new file mode 100644 index 00000000..9c8ef0cd --- /dev/null +++ b/lib/pages/roles_and_permission/view/roles_page.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/view/role_card.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 RolesPage extends StatelessWidget { + final RolesPermissionBloc blocRole; + const RolesPage({super.key, required this.blocRole}); + + @override + Widget build(BuildContext context) { + final TextEditingController searchController = TextEditingController(); + double screenWidth = MediaQuery.of(context).size.width; + + int crossAxisCount = (screenWidth ~/ 200).clamp(1, 6); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: containerDecoration.copyWith( + borderRadius: const BorderRadius.all( + Radius.circular(20), + ), + ), + width: 250, + child: TextFormField( + controller: searchController, + style: const TextStyle(color: Colors.black), + decoration: textBoxDecoration(radios: 15)!.copyWith( + fillColor: ColorsManager.whiteColors, + errorStyle: const TextStyle(height: 0), + hintStyle: context.textTheme.titleSmall?.copyWith( + color: Colors.grey, + fontSize: 12, + ), + hintText: 'Search', + suffixIcon: SvgPicture.asset(Assets.searchIconUser)), + ), + ), + Expanded( + child: GridView.builder( + padding: const EdgeInsets.all(10), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + crossAxisSpacing: 16, + mainAxisSpacing: 16, + childAspectRatio: 2 / 2.5, + ), + itemCount: blocRole.roleModel.length ?? 0, + itemBuilder: (context, index) { + final role = blocRole.roleModel[index]; + if (role == null) { + return const SizedBox.shrink(); + } + return RoleCard( + name: role.roleName ?? 'Unknown', + ); + }, + ), + ), + ], + ); + } +} diff --git a/lib/utils/app_routes.dart b/lib/utils/app_routes.dart index 20a89e21..246e269e 100644 --- a/lib/utils/app_routes.dart +++ b/lib/utils/app_routes.dart @@ -3,6 +3,7 @@ import 'package:syncrow_web/pages/access_management/view/access_management.dart' import 'package:syncrow_web/pages/auth/view/login_page.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_page.dart'; import 'package:syncrow_web/pages/home/view/home_page.dart'; +import 'package:syncrow_web/pages/roles_and_permission/view/roles_and_permission_page.dart'; import 'package:syncrow_web/pages/spaces_management/view/spaces_management_page.dart'; import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; import 'package:syncrow_web/utils/constants/routes_const.dart'; @@ -32,7 +33,10 @@ class AppRoutes { ), GoRoute( path: RoutesConst.spacesManagementPage, - builder: (context, state) => SpaceManagementPage()), + builder: (context, state) => const SpaceManagementPage()), + GoRoute( + path: RoutesConst.rolesAndPermissions, + builder: (context, state) => const RolesAndPermissionPage()), ]; } } diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 95d0f214..5549b566 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -5,7 +5,8 @@ abstract class ColorsManager { static const Color switchOffColor = Color(0x7F8D99AE); static const Color primaryColor = Color(0xFF0030CB); //023DFE static const Color secondaryTextColor = Color(0xFF848484); - static Color primaryColorWithOpacity = const Color(0xFF023DFE).withOpacity(0.6); + static Color primaryColorWithOpacity = + const Color(0xFF023DFE).withOpacity(0.6); static const Color whiteColors = Colors.white; static const Color secondaryColor = Color(0xFF023DFE); static const Color onSecondaryColor = Color(0xFF023DFE); @@ -54,5 +55,13 @@ abstract class ColorsManager { static const Color warningRed = Color(0xFFFF6465); static const Color borderColor = Color(0xFFE5E5E5); static const Color CircleImageBackground = Color(0xFFF4F4F4); + static const Color CircleRolesBackground = Color(0xFFF8F8F8); + static const Color activeGreen = Color(0xFF99FF93); + static const Color activeGreenText = Color(0xFF008905); + static const Color disabledPink = Color(0xFFFF9395); + static const Color disabledRedText = Color(0xFF890002); + static const Color invitedOrange = Color(0xFFFFE193); + static const Color invitedOrangeText = Color(0xFFFFBF00); + //background: #F8F8F8; + } -//background: #background: #5D5D5D; diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 958c2c1c..4e35e84f 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -13,10 +13,12 @@ class Assets { static const String rightLine = "assets/images/right_line.png"; static const String google = "assets/images/google.svg"; static const String facebook = "assets/images/facebook.svg"; - static const String invisiblePassword = "assets/images/Password_invisible.svg"; + static const String invisiblePassword = + "assets/images/Password_invisible.svg"; static const String visiblePassword = "assets/images/password_visible.svg"; static const String accessIcon = "assets/images/access_icon.svg"; - static const String spaseManagementIcon = "assets/images/spase_management_icon.svg"; + static const String spaseManagementIcon = + "assets/images/spase_management_icon.svg"; static const String devicesIcon = "assets/images/devices_icon.svg"; static const String moveinIcon = "assets/images/movein_icon.svg"; static const String constructionIcon = "assets/images/construction_icon.svg"; @@ -29,13 +31,15 @@ class Assets { static const String emptyTable = "assets/images/empty_table.svg"; // General assets - static const String motionlessDetection = "assets/icons/motionless_detection.svg"; + static const String motionlessDetection = + "assets/icons/motionless_detection.svg"; static const String acHeating = "assets/icons/ac_heating.svg"; static const String acPowerOff = "assets/icons/ac_power_off.svg"; static const String acFanMiddle = "assets/icons/ac_fan_middle.svg"; static const String switchAlarmSound = "assets/icons/switch_alarm_sound.svg"; static const String resetOff = "assets/icons/reset_off.svg"; - static const String sensitivityOperationIcon = "assets/icons/sesitivity_operation_icon.svg"; + static const String sensitivityOperationIcon = + "assets/icons/sesitivity_operation_icon.svg"; static const String motionDetection = "assets/icons/motion_detection.svg"; static const String freezing = "assets/icons/freezing.svg"; static const String indicator = "assets/icons/indicator.svg"; @@ -56,7 +60,8 @@ class Assets { static const String celsiusDegrees = "assets/icons/celsius_degrees.svg"; static const String masterState = "assets/icons/master_state.svg"; static const String acPower = "assets/icons/ac_power.svg"; - static const String farDetectionFunction = "assets/icons/far_detection_function.svg"; + static const String farDetectionFunction = + "assets/icons/far_detection_function.svg"; static const String nobodyTime = "assets/icons/nobody_time.svg"; // Automation functions @@ -64,33 +69,47 @@ class Assets { "assets/icons/automation_functions/temp_password_unlock.svg"; static const String doorlockNormalOpen = "assets/icons/automation_functions/doorlock_normal_open.svg"; - static const String doorbell = "assets/icons/automation_functions/doorbell.svg"; + static const String doorbell = + "assets/icons/automation_functions/doorbell.svg"; static const String remoteUnlockViaApp = "assets/icons/automation_functions/remote_unlock_via_app.svg"; - static const String doubleLock = "assets/icons/automation_functions/double_lock.svg"; - static const String selfTestResult = "assets/icons/automation_functions/self_test_result.svg"; - static const String lockAlarm = "assets/icons/automation_functions/lock_alarm.svg"; - static const String presenceState = "assets/icons/automation_functions/presence_state.svg"; - static const String currentTemp = "assets/icons/automation_functions/current_temp.svg"; - static const String presence = "assets/icons/automation_functions/presence.svg"; + static const String doubleLock = + "assets/icons/automation_functions/double_lock.svg"; + static const String selfTestResult = + "assets/icons/automation_functions/self_test_result.svg"; + static const String lockAlarm = + "assets/icons/automation_functions/lock_alarm.svg"; + static const String presenceState = + "assets/icons/automation_functions/presence_state.svg"; + static const String currentTemp = + "assets/icons/automation_functions/current_temp.svg"; + static const String presence = + "assets/icons/automation_functions/presence.svg"; static const String residualElectricity = "assets/icons/automation_functions/residual_electricity.svg"; - static const String hijackAlarm = "assets/icons/automation_functions/hijack_alarm.svg"; - static const String passwordUnlock = "assets/icons/automation_functions/password_unlock.svg"; + static const String hijackAlarm = + "assets/icons/automation_functions/hijack_alarm.svg"; + static const String passwordUnlock = + "assets/icons/automation_functions/password_unlock.svg"; static const String remoteUnlockRequest = "assets/icons/automation_functions/remote_unlock_req.svg"; - static const String cardUnlock = "assets/icons/automation_functions/card_unlock.svg"; + static const String cardUnlock = + "assets/icons/automation_functions/card_unlock.svg"; static const String motion = "assets/icons/automation_functions/motion.svg"; static const String fingerprintUnlock = "assets/icons/automation_functions/fingerprint_unlock.svg"; // Presence Sensor Assets static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg"; - static const String sensorPresenceIcon = "assets/icons/sensor_presence_ic.svg"; + static const String sensorPresenceIcon = + "assets/icons/sensor_presence_ic.svg"; static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg"; - static const String illuminanceRecordIcon = "assets/icons/illuminance_record_ic.svg"; - static const String presenceRecordIcon = "assets/icons/presence_record_ic.svg"; - static const String helpDescriptionIcon = "assets/icons/help_description_ic.svg"; + static const String illuminanceRecordIcon = + "assets/icons/illuminance_record_ic.svg"; + static const String presenceRecordIcon = + "assets/icons/presence_record_ic.svg"; + static const String helpDescriptionIcon = + "assets/icons/help_description_ic.svg"; static const String lightPulp = "assets/icons/light_pulb.svg"; static const String acDevice = "assets/icons/ac_device.svg"; @@ -140,10 +159,12 @@ class Assets { static const String unit = 'assets/icons/unit_icon.svg'; static const String villa = 'assets/icons/villa_icon.svg'; static const String iconEdit = 'assets/icons/icon_edit_icon.svg'; - static const String textFieldSearch = 'assets/icons/textfield_search_icon.svg'; + static const String textFieldSearch = + 'assets/icons/textfield_search_icon.svg'; static const String roundedAddIcon = 'assets/icons/rounded_add_icon.svg'; static const String addIcon = 'assets/icons/add_icon.svg'; - static const String smartThermostatIcon = 'assets/icons/smart_thermostat_icon.svg'; + static const String smartThermostatIcon = + 'assets/icons/smart_thermostat_icon.svg'; static const String smartLightIcon = 'assets/icons/smart_light_icon.svg'; static const String presenceSensor = 'assets/icons/presence_sensor.svg'; static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg'; @@ -191,7 +212,8 @@ class Assets { //assets/icons/water_leak_normal.svg static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg'; //assets/icons/water_leak_detected.svg - static const String waterLeakDetected = 'assets/icons/water_leak_detected.svg'; + static const String waterLeakDetected = + 'assets/icons/water_leak_detected.svg'; //assets/icons/automation_records.svg static const String automationRecords = 'assets/icons/automation_records.svg'; @@ -256,40 +278,64 @@ class Assets { static const String delay = 'assets/icons/routine/delay.svg'; // Assets for functions_icons - static const String assetsSensitivityFunction = "assets/icons/functions_icons/sensitivity.svg"; + static const String assetsSensitivityFunction = + "assets/icons/functions_icons/sensitivity.svg"; static const String assetsSensitivityOperationIcon = "assets/icons/functions_icons/sesitivity_operation_icon.svg"; - static const String assetsAcPower = "assets/icons/functions_icons/ac_power.svg"; - static const String assetsAcPowerOFF = "assets/icons/functions_icons/ac_power_off.svg"; - static const String assetsChildLock = "assets/icons/functions_icons/child_lock.svg"; - static const String assetsFreezing = "assets/icons/functions_icons/freezing.svg"; - static const String assetsFanSpeed = "assets/icons/functions_icons/fan_speed.svg"; - static const String assetsAcCooling = "assets/icons/functions_icons/ac_cooling.svg"; - static const String assetsAcHeating = "assets/icons/functions_icons/ac_heating.svg"; - static const String assetsCelsiusDegrees = "assets/icons/functions_icons/celsius_degrees.svg"; - static const String assetsTempreture = "assets/icons/functions_icons/tempreture.svg"; - static const String assetsAcFanLow = "assets/icons/functions_icons/ac_fan_low.svg"; - static const String assetsAcFanMiddle = "assets/icons/functions_icons/ac_fan_middle.svg"; - static const String assetsAcFanHigh = "assets/icons/functions_icons/ac_fan_high.svg"; - static const String assetsAcFanAuto = "assets/icons/functions_icons/ac_fan_auto.svg"; - static const String assetsSceneChildLock = "assets/icons/functions_icons/scene_child_lock.svg"; + static const String assetsAcPower = + "assets/icons/functions_icons/ac_power.svg"; + static const String assetsAcPowerOFF = + "assets/icons/functions_icons/ac_power_off.svg"; + static const String assetsChildLock = + "assets/icons/functions_icons/child_lock.svg"; + static const String assetsFreezing = + "assets/icons/functions_icons/freezing.svg"; + static const String assetsFanSpeed = + "assets/icons/functions_icons/fan_speed.svg"; + static const String assetsAcCooling = + "assets/icons/functions_icons/ac_cooling.svg"; + static const String assetsAcHeating = + "assets/icons/functions_icons/ac_heating.svg"; + static const String assetsCelsiusDegrees = + "assets/icons/functions_icons/celsius_degrees.svg"; + static const String assetsTempreture = + "assets/icons/functions_icons/tempreture.svg"; + static const String assetsAcFanLow = + "assets/icons/functions_icons/ac_fan_low.svg"; + static const String assetsAcFanMiddle = + "assets/icons/functions_icons/ac_fan_middle.svg"; + static const String assetsAcFanHigh = + "assets/icons/functions_icons/ac_fan_high.svg"; + static const String assetsAcFanAuto = + "assets/icons/functions_icons/ac_fan_auto.svg"; + static const String assetsSceneChildLock = + "assets/icons/functions_icons/scene_child_lock.svg"; static const String assetsSceneChildUnlock = "assets/icons/functions_icons/scene_child_unlock.svg"; - static const String assetsSceneRefresh = "assets/icons/functions_icons/scene_refresh.svg"; - static const String assetsLightCountdown = "assets/icons/functions_icons/light_countdown.svg"; - static const String assetsFarDetection = "assets/icons/functions_icons/far_detection.svg"; + static const String assetsSceneRefresh = + "assets/icons/functions_icons/scene_refresh.svg"; + static const String assetsLightCountdown = + "assets/icons/functions_icons/light_countdown.svg"; + static const String assetsFarDetection = + "assets/icons/functions_icons/far_detection.svg"; static const String assetsFarDetectionFunction = "assets/icons/functions_icons/far_detection_function.svg"; - static const String assetsIndicator = "assets/icons/functions_icons/indicator.svg"; - static const String assetsMotionDetection = "assets/icons/functions_icons/motion_detection.svg"; + static const String assetsIndicator = + "assets/icons/functions_icons/indicator.svg"; + static const String assetsMotionDetection = + "assets/icons/functions_icons/motion_detection.svg"; static const String assetsMotionlessDetection = "assets/icons/functions_icons/motionless_detection.svg"; - static const String assetsNobodyTime = "assets/icons/functions_icons/nobody_time.svg"; - static const String assetsFactoryReset = "assets/icons/functions_icons/factory_reset.svg"; - static const String assetsMasterState = "assets/icons/functions_icons/master_state.svg"; + static const String assetsNobodyTime = + "assets/icons/functions_icons/nobody_time.svg"; + static const String assetsFactoryReset = + "assets/icons/functions_icons/factory_reset.svg"; + static const String assetsMasterState = + "assets/icons/functions_icons/master_state.svg"; static const String assetsSwitchAlarmSound = "assets/icons/functions_icons/switch_alarm_sound.svg"; - static const String assetsResetOff = "assets/icons/functions_icons/reset_off.svg"; + static const String assetsResetOff = + "assets/icons/functions_icons/reset_off.svg"; // Assets for automation_functions static const String assetsCardUnlock = @@ -320,11 +366,29 @@ class Assets { "assets/icons/functions_icons/automation_functions/self_test_result.svg"; static const String assetsPresence = "assets/icons/functions_icons/automation_functions/presence.svg"; - static const String assetsMotion = "assets/icons/functions_icons/automation_functions/motion.svg"; + static const String assetsMotion = + "assets/icons/functions_icons/automation_functions/motion.svg"; static const String assetsCurrentTemp = "assets/icons/functions_icons/automation_functions/current_temp.svg"; static const String assetsPresenceState = "assets/icons/functions_icons/automation_functions/presence_state.svg"; //assets/icons/routine/automation.svg static const String automation = 'assets/icons/routine/automation.svg'; + static const String searchIconUser = 'assets/icons/search_icon_user.svg'; + static const String searchIcoUser = 'assets/icons/search_icon_user.svg'; + static const String activeUser = 'assets/icons/active_user.svg'; + static const String deActiveUser = 'assets/icons/deactive_user.svg'; + static const String invitedIcon = 'assets/icons/invited_icon.svg'; + static const String rectangleCheckBox = + 'assets/icons/rectangle_check_box.png'; + static const String CheckBoxChecked = 'assets/icons/box_checked.png'; + static const String emptyBox = 'assets/icons/empty_box.png'; + static const String completeProcessIcon = + 'assets/icons/compleate_process_icon.svg'; + static const String currentProcessIcon = + 'assets/icons/current_process_icon.svg'; + static const String uncomplete_ProcessIcon = + 'assets/icons/uncompleate_process_icon.svg'; + static const String wrongProcessIcon = + 'assets/icons/wrong_process_icon.svg'; } diff --git a/lib/utils/constants/routes_const.dart b/lib/utils/constants/routes_const.dart index 094787d4..8a65e9ae 100644 --- a/lib/utils/constants/routes_const.dart +++ b/lib/utils/constants/routes_const.dart @@ -5,4 +5,5 @@ class RoutesConst { static const String accessManagementPage = '/access-management-page'; static const String deviceManagementPage = '/device-management-page'; static const String spacesManagementPage = '/spaces_management-page'; + static const String rolesAndPermissions = '/roles_and_Permissions-page'; } diff --git a/lib/utils/style.dart b/lib/utils/style.dart index a80c68d6..b5ea59ee 100644 --- a/lib/utils/style.dart +++ b/lib/utils/style.dart @@ -2,51 +2,59 @@ import 'package:flutter/material.dart'; import 'color_manager.dart'; -InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration( +InputDecoration? textBoxDecoration( + {bool suffixIcon = false, double radios = 8}) => + InputDecoration( focusColor: ColorsManager.grayColor, suffixIcon: suffixIcon ? const Icon(Icons.search) : null, hintText: 'Search', filled: true, // Enable background filling fillColor: const Color(0xffF5F6F7), // Set the background color border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), // Add border radius + borderRadius: BorderRadius.circular(radios), // Add border radius borderSide: BorderSide.none, // Remove the underline ), enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), // Add border radius + borderRadius: BorderRadius.circular(radios), // Add border radius borderSide: BorderSide.none, // Remove the underline ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), // Add border radius + borderRadius: BorderRadius.circular(radios), // Add border radius borderSide: BorderSide.none, // Remove the underline ), errorBorder: OutlineInputBorder( borderSide: const BorderSide(color: Colors.red, width: 2), - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(radios), ), focusedErrorBorder: OutlineInputBorder( borderSide: const BorderSide(color: Colors.red, width: 2), - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(radios), ), ); -BoxDecoration containerDecoration = BoxDecoration(boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.3), - spreadRadius: 2, - blurRadius: 4, - offset: const Offset(0, 5), // changes position of shadow - ), -], color: ColorsManager.boxColor, borderRadius: const BorderRadius.all(Radius.circular(10))); +BoxDecoration containerDecoration = BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.3), + spreadRadius: 2, + blurRadius: 4, + offset: const Offset(0, 5), // changes position of shadow + ), + ], + color: ColorsManager.boxColor, + borderRadius: const BorderRadius.all(Radius.circular(10))); -BoxDecoration containerWhiteDecoration = BoxDecoration(boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.3), - spreadRadius: 2, - blurRadius: 4, - offset: const Offset(0, 5), // changes position of shadow - ), -], color: ColorsManager.whiteColors, borderRadius: const BorderRadius.all(Radius.circular(15))); +BoxDecoration containerWhiteDecoration = BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.3), + spreadRadius: 2, + blurRadius: 4, + offset: const Offset(0, 5), // changes position of shadow + ), + ], + color: ColorsManager.whiteColors, + borderRadius: const BorderRadius.all(Radius.circular(15))); BoxDecoration subSectionContainerDecoration = BoxDecoration( color: ColorsManager.whiteColors, @@ -59,3 +67,30 @@ BoxDecoration subSectionContainerDecoration = BoxDecoration( ), ], ); + +InputDecoration inputTextFormDeco({hintText}) => InputDecoration( + hintText: hintText, + border: const OutlineInputBorder( + + borderSide: BorderSide( + width: 1, + color: ColorsManager.textGray, // Border color for unfocused state + ), + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide( + width: 2, + color: ColorsManager.textGray, // Border color when focused + ), + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide( + width: 1, + color: ColorsManager + .textGray // Border color for enabled (but unfocused) state + ), + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + ); diff --git a/pubspec.lock b/pubspec.lock index 192106d7..ea2315d7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -316,18 +316,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.4" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.3" leak_tracker_testing: dependency: transitive description: @@ -364,18 +364,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.12.0" nested: dependency: transitive description: @@ -593,10 +593,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.0" time_picker_spinner: dependency: "direct main" description: @@ -657,10 +657,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.2.1" web: dependency: transitive description: From 879bf99b12243232606a9c9869222793cfbfc5cf Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 16 Dec 2024 16:37:55 +0300 Subject: [PATCH 011/175] add_user_dialog --- assets/icons/arrow_down.svg | 3 + assets/icons/arrow_forward.svg | 3 + .../users_page/bloc/users_bloc.dart | 160 +++++++++++++--- .../users_page/bloc/users_event.dart | 16 ++ .../users_page/model/tree_node_model.dart | 17 ++ .../users_page/view/add_user_dialog.dart | 155 ++++++++++++--- .../users_page/view/roles_and_permission.dart | 2 + .../users_page/view/spaces_access_view.dart | 178 ++++++------------ lib/services/space_mana_api.dart | 1 + lib/utils/constants/assets.dart | 4 + 10 files changed, 369 insertions(+), 170 deletions(-) create mode 100644 assets/icons/arrow_down.svg create mode 100644 assets/icons/arrow_forward.svg create mode 100644 lib/pages/roles_and_permission/users_page/model/tree_node_model.dart diff --git a/assets/icons/arrow_down.svg b/assets/icons/arrow_down.svg new file mode 100644 index 00000000..2b4be77b --- /dev/null +++ b/assets/icons/arrow_down.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/arrow_forward.svg b/assets/icons/arrow_forward.svg new file mode 100644 index 00000000..e5866360 --- /dev/null +++ b/assets/icons/arrow_forward.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart index 5de74e4b..0d153804 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -3,12 +3,18 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_node_model.dart'; +import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; +import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; +import 'package:syncrow_web/services/space_mana_api.dart'; class UsersBloc extends Bloc { UsersBloc() : super(UsersInitial()) { on(_getUsers); on(_changeUserStatus); on(isCompleteBasicsFun); + on(_onLoadCommunityAndSpaces); + on(searchTreeNode); } List users = []; @@ -86,9 +92,9 @@ class UsersBloc extends Bloc { final TextEditingController phoneController = TextEditingController(); final TextEditingController jobTitleController = TextEditingController(); - bool isCompleteBasics = false; - bool isCompleteRolePermissions = false; - bool isCompleteSpaces = false; + bool? isCompleteBasics; + bool? isCompleteRolePermissions; + bool? isCompleteSpaces; int numberBasics = 0; int numberSpaces = 0; @@ -102,35 +108,137 @@ class UsersBloc extends Bloc { phoneController.text.isNotEmpty && jobTitleController.text.isNotEmpty; emit(ChangeStatusSteps()); + return isCompleteBasics; } - // void checkStatus(CheckStepStatus event, Emitter emit) { - // try { - // // Check if basic fields are completed - // isCompleteBasics = firstNameController.text.isNotEmpty && - // lastNameController.text.isNotEmpty && - // emailController.text.isNotEmpty && - // phoneController.text.isNotEmpty && - // jobTitleController.text.isNotEmpty; - // // Emit the updated state - // if (isCompleteBasics && isCompleteRolePermissions && isCompleteSpaces) { - // } else { - // // emit(IncompleteState( - // // isCompleteBasics, isCompleteRolePermissions, isCompleteSpaces)); - // } - // } catch (e) { - // emit(ErrorState(e.toString())); - // } - // } + isCompleteSpacesFun(CheckStepStatus event, Emitter emit) { + emit(UsersLoadingState()); + isCompleteSpaces = false; + emit(ChangeStatusSteps()); + print('isCompleteBasics==$isCompleteSpaces'); + return isCompleteSpaces; + } -// Example placeholder methods for role permissions and spaces bool checkRolePermissions() { - // Add logic to check if role permissions are completed - return true; // Replace with actual logic + return true; } bool checkSpaces() { - // Add logic to check if spaces are completed - return true; // Replace with actual logic + return true; + } + + Future> _fetchSpacesForCommunity( + String communityUuid) async { + return await CommunitySpaceManagementApi().getSpaceHierarchy(communityUuid); + } + + List updatedCommunities = []; + List spacesNodes = []; + _onLoadCommunityAndSpaces( + LoadCommunityAndSpacesEvent event, + Emitter emit, + ) async { + emit(UsersLoadingState()); // Emit loading state + try { + // Fetch the list of communities + List communities = + await CommunitySpaceManagementApi().fetchCommunities(); + + // Fetch spaces and create TreeNodes for each community + updatedCommunities = await Future.wait( + communities.map((community) async { + // Fetch spaces for the current community + List spaces = + await _fetchSpacesForCommunity(community.uuid); + + // Recursively build the tree structure + spacesNodes = _buildTreeNodes(spaces); + + // Return a TreeNode for the community, with spaces as its children + return TreeNode( + uuid: community.uuid, + title: community.name, + children: spacesNodes, + isChecked: false, // Initial state; can be updated later + isHighlighted: false, + isExpanded: true, // Default to expanded for better UX + ); + }).toList(), + ); + + // Emit the final state with the structured tree + emit(ChangeStatusSteps()); + + return updatedCommunities; // Return the structured data if needed + } catch (e) { + // Emit error state in case of failure + emit(ErrorState('Error loading communities and spaces: $e')); + } + } + +// Helper function to recursively build tree nodes + List _buildTreeNodes(List spaces) { + return spaces.map((space) { + // If the space has children, recursively build nodes for them + List childNodes = + space.children != null ? _buildTreeNodes(space.children!) : []; + + // Create a TreeNode for the current space + return TreeNode( + uuid: space.uuid!, + title: space.name, + isChecked: false, + isHighlighted: false, + isExpanded: childNodes.isNotEmpty, // Expand if there are children + children: childNodes, + ); + }).toList(); + } + + void searchTreeNode(SearchAnode event, Emitter emit) { + emit(UsersLoadingState()); // Emit loading state + + // Clear all highlights if the search term is empty + if (event.searchTerm!.isEmpty) { + _clearHighlights(updatedCommunities); + } else { + // Perform the search and update the highlights + _searchAndHighlightNodes(updatedCommunities, event.searchTerm!); + } + + // Emit the updated state after processing all nodes + emit(ChangeStatusSteps()); + } + +// Helper function to clear all highlights in the tree + void _clearHighlights(List nodes) { + for (var node in nodes) { + node.isHighlighted = false; + if (node.children.isNotEmpty) { + _clearHighlights(node.children); + } + } + } + +// Helper function to search and highlight nodes recursively + bool _searchAndHighlightNodes(List nodes, String searchTerm) { + bool anyMatch = false; + + for (var node in nodes) { + // Check if this node matches the search term + bool isMatch = + node.title.toLowerCase().contains(searchTerm.toLowerCase()); + + // Recursively check children for matches + bool childMatch = _searchAndHighlightNodes(node.children, searchTerm); + + // Highlight this node if it matches or any of its children match + node.isHighlighted = isMatch || childMatch; + + // Update if any matches were found in this branch + anyMatch = anyMatch || node.isHighlighted; + } + + return anyMatch; } } diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart index 3b679a7e..77890673 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_node_model.dart'; sealed class UsersEvent extends Equatable { const UsersEvent(); @@ -10,6 +11,12 @@ class GetUsers extends UsersEvent { List get props => []; } +class LoadCommunityAndSpacesEvent extends UsersEvent { + const LoadCommunityAndSpacesEvent(); + @override + List get props => []; +} + class GetBatchStatus extends UsersEvent { final List uuids; const GetBatchStatus(this.uuids); @@ -17,6 +24,7 @@ class GetBatchStatus extends UsersEvent { List get props => [uuids]; } +//LoadCommunityAndSpacesEvent class ChangeUserStatus extends UsersEvent { final String userId; final String newStatus; @@ -33,3 +41,11 @@ class CheckStepStatus extends UsersEvent { @override List get props => [steps]; } + +class SearchAnode extends UsersEvent { + List? nodes; + String? searchTerm; + SearchAnode({this.nodes, this.searchTerm}); + @override + List get props => [nodes, searchTerm]; +} diff --git a/lib/pages/roles_and_permission/users_page/model/tree_node_model.dart b/lib/pages/roles_and_permission/users_page/model/tree_node_model.dart new file mode 100644 index 00000000..a5e622dc --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/model/tree_node_model.dart @@ -0,0 +1,17 @@ +class TreeNode { + String uuid; + String title; + bool isChecked; + bool isHighlighted; + bool isExpanded; + List children; + + TreeNode({ + required this.uuid, + required this.title, + this.isChecked = false, + this.isHighlighted = false, + this.isExpanded = false, + this.children = const [], + }); +} \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart index b2ee8cf5..21e92727 100644 --- a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/view/basics_view.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart'; @@ -22,7 +23,7 @@ class _AddNewUserDialogState extends State { @override Widget build(BuildContext context) { return BlocProvider( - create: (BuildContext context) => UsersBloc(), + create: (BuildContext context) => UsersBloc()..add(LoadCommunityAndSpacesEvent()), child: BlocConsumer( listener: (context, state) {}, builder: (context, state) { @@ -58,9 +59,9 @@ class _AddNewUserDialogState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildStepIndicator(1, "Basics", _blocRole), - _buildStepIndicator(2, "Spaces", _blocRole), - _buildStepIndicator( + _buildStep1Indicator(1, "Basics", _blocRole), + _buildStep2Indicator(2, "Spaces", _blocRole), + _buildStep3Indicator( 3, "Role & Permissions", _blocRole), ], ), @@ -120,14 +121,13 @@ class _AddNewUserDialogState extends State { ), ], ), - ), - ], - ), - )); - })); + ), + ], + ), + )); + })); } - // Method to get the form content based on the current step Widget _getFormContent() { switch (currentStep) { case 1: @@ -141,22 +141,70 @@ class _AddNewUserDialogState extends State { } } - // Helper method to build step indicators - Widget _buildStepIndicator(int step, String label, UsersBloc bloc) { + Widget _buildStep2Indicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + setState(() { + currentStep = step; + bloc.add(const CheckStepStatus()); + }); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + SvgPicture.asset( + currentStep == step + ? Assets.currentProcessIcon + : bloc.isCompleteSpaces == false + ? Assets.wrongProcessIcon + : Assets.uncomplete_ProcessIcon, + width: 25, + height: 25, + ), + const SizedBox(width: 10), + Text( + label, + style: TextStyle( + fontSize: 16, + color: currentStep == step + ? ColorsManager.blackColor + : ColorsManager.greyColor, + fontWeight: currentStep == step + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ], + ), + ), + if (step != 3) + Padding( + padding: const EdgeInsets.all(5.0), + child: Padding( + padding: const EdgeInsets.only(left: 12), + child: Container( + height: 60, + width: 1, + color: Colors.grey, + ), + ), + ) + ], + ), + ); + } + + Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) { return GestureDetector( onTap: () { - // if (bloc.formKey.currentState?.validate() ?? false) { setState(() { currentStep = step; - if (currentStep == 1) { - bloc.numberBasics = 1; - } else if (currentStep == 2) { - bloc.numberSpaces = 2; - } else if (currentStep == 3) { - bloc.numberRole = 3; - } }); - // } }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -170,12 +218,67 @@ class _AddNewUserDialogState extends State { currentStep == step ? Assets.currentProcessIcon : bloc.isCompleteBasics == false - && (bloc.numberBasics != 0 || - bloc.numberRole != 0 || - bloc.numberSpaces != 0) - ? Assets.wrongProcessIcon + ? Assets.wrongProcessIcon + : bloc.isCompleteBasics == true + ? Assets.completeProcessIcon : Assets.uncomplete_ProcessIcon, - + width: 25, + height: 25, + ), + const SizedBox(width: 10), + Text( + label, + style: TextStyle( + fontSize: 16, + color: currentStep == step + ? ColorsManager.blackColor + : ColorsManager.greyColor, + fontWeight: currentStep == step + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ], + ), + ), + if (step != 3) + Padding( + padding: const EdgeInsets.all(5.0), + child: Padding( + padding: const EdgeInsets.only(left: 12), + child: Container( + height: 60, + width: 1, + color: Colors.grey, + ), + ), + ) + ], + ), + ); + } + + Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + setState(() { + currentStep = step; + }); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + SvgPicture.asset( + currentStep == step + ? Assets.currentProcessIcon + : bloc.isCompleteRolePermissions == false + ? Assets.wrongProcessIcon + : Assets.uncomplete_ProcessIcon, width: 25, height: 25, ), diff --git a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart index c9b58581..812a3170 100644 --- a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart +++ b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart @@ -199,6 +199,8 @@ class DeviceManagement extends StatefulWidget { } class _DeviceManagementState extends State { + + final List options = [ MainRoleOption( id: '1', diff --git a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart index 9dc35a89..5dff9b0a 100644 --- a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart @@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_node_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'; @@ -68,7 +70,12 @@ class SpacesAccessView extends StatelessWidget { child: TextFormField( style: const TextStyle(color: Colors.black), - controller: _blocRole.firstNameController, + // controller: _blocRole.firstNameController, + onChanged: (value) { + _blocRole.add(SearchAnode( + nodes: _blocRole.updatedCommunities, + searchTerm: value)); + }, decoration: textBoxDecoration(radios: 20)! .copyWith( fillColor: Colors.white, @@ -102,7 +109,9 @@ class SpacesAccessView extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: Container( color: ColorsManager.whiteColors, - child: TreeView()))) + child: TreeView( + bloc: _blocRole, + )))) ], ), ), @@ -115,90 +124,15 @@ class SpacesAccessView extends StatelessWidget { } } -class TreeNode { - String title; - bool isChecked; - bool isHighlighted; - bool isExpanded; // New property to manage expansion - List children; - - TreeNode({ - required this.title, - this.isChecked = false, - this.isHighlighted = false, - this.isExpanded = false, // Default to collapsed - this.children = const [], - }); -} - +// ignore: must_be_immutable class TreeView extends StatefulWidget { + UsersBloc? bloc; + TreeView({super.key, this.bloc}); @override _TreeViewState createState() => _TreeViewState(); } class _TreeViewState extends State { - List treeData = [ - TreeNode( - title: 'Downtown Dubai', - ), - TreeNode(title: 'Dubai Creek Harbour', isHighlighted: true), - TreeNode( - title: 'Dubai Hills Estate', - isHighlighted: true, - children: [ - TreeNode(title: 'North Side'), - TreeNode( - title: 'South Side', - isHighlighted: true, - children: [ - TreeNode(title: 'Hills Business Park'), - TreeNode(title: 'Park Point'), - TreeNode(title: 'Acacia'), - TreeNode( - title: 'Executive Residence', - children: [ - TreeNode(title: 'Residence I'), - TreeNode( - title: 'Residence II', - children: [ - TreeNode(title: 'Ground Floor', isHighlighted: true), - TreeNode(title: '1st Floor', isHighlighted: true), - TreeNode(title: 'Pool', isHighlighted: true), - TreeNode(title: 'Gym', isHighlighted: true), - ], - ), - ], - ), - ], - ), - TreeNode( - title: 'South Side', - isHighlighted: true, - children: [ - TreeNode(title: 'Hills Business Park'), - TreeNode(title: 'Park Point'), - TreeNode(title: 'Acacia'), - TreeNode( - title: 'Executive Residence', - children: [ - TreeNode(title: 'Residence I'), - TreeNode( - title: 'Residence II', - children: [ - TreeNode(title: 'Ground Floor', isHighlighted: true), - TreeNode(title: '1st Floor', isHighlighted: true), - TreeNode(title: 'Pool', isHighlighted: true), - TreeNode(title: 'Gym', isHighlighted: true), - ], - ), - ], - ), - ], - ), - ], - ), - ]; - Widget _buildTree(List nodes, {int level = 0}) { return Column( children: nodes.map((node) => _buildNode(node, level: level)).toList(), @@ -207,31 +141,14 @@ class _TreeViewState extends State { Widget _buildNode(TreeNode node, {int level = 0}) { return Container( - color: node.isHighlighted ? Colors.blue.shade100 : Colors.transparent, - child: Padding( - padding: EdgeInsets.only(left: level * 8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( + color: node.isHighlighted ? Colors.blue.shade50 : Colors.transparent, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( children: [ - GestureDetector( - onTap: () { - setState(() { - node.isExpanded = !node.isExpanded; // Toggle expansion - }); - }, - child: Icon( - node.children.isNotEmpty - ? (node.isExpanded - ? Icons.arrow_drop_down - : Icons.arrow_right) - : Icons.arrow_right, - color: node.children.isNotEmpty - ? Colors.black - : Colors.transparent, - ), - ), GestureDetector( onTap: () { setState(() { @@ -246,24 +163,49 @@ class _TreeViewState extends State { height: 20, ), ), + const SizedBox(width: 15), Expanded( - child: Text( - node.title, - style: TextStyle( - fontSize: 16, - color: Colors.black87, + child: Padding( + padding: EdgeInsets.only(left: level * 10.0), + child: Row( + children: [ + GestureDetector( + onTap: () { + setState(() { + node.isExpanded = !node.isExpanded; + }); + }, + child: SizedBox( + child: SvgPicture.asset( + node.children.isNotEmpty + ? (node.isExpanded + ? Assets.arrowDown + : Assets.arrowForward) + : Assets.arrowForward, + fit: BoxFit.none, + ), + ), + ), + const SizedBox(width: 20), + Text( + node.title, + style: TextStyle( + fontSize: 16, + color: node.isHighlighted + ? ColorsManager.blackColor + : ColorsManager.textGray, + ), + ), + ], ), ), ), ], ), - if (node.isExpanded && node.children.isNotEmpty) - Padding( - padding: const EdgeInsets.only(left: 24.0), - child: _buildTree(node.children, level: level + 1), - ), - ], - ), + ), + if (node.isExpanded && node.children.isNotEmpty) + _buildTree(node.children, level: level + 1), + ], ), ); } @@ -308,7 +250,7 @@ class _TreeViewState extends State { // Update the checkbox state for parent nodes void _updateParentCheckStatus(TreeNode node) { - TreeNode? parent = _findParent(treeData, node); + TreeNode? parent = _findParent(widget.bloc!.updatedCommunities, node); if (parent != null) { setState(() { parent.isChecked = _areAllChildrenChecked(parent); @@ -332,7 +274,7 @@ class _TreeViewState extends State { @override Widget build(BuildContext context) { return SingleChildScrollView( - child: _buildTree(treeData), + child: _buildTree(widget.bloc!.updatedCommunities), ); } } diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 5d2464e6..58dc0d9d 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -252,6 +252,7 @@ class CommunitySpaceManagementApi { path: ApiEndpoints.getSpaceHierarchy .replaceAll('{communityId}', communityId), expectedResponseModel: (json) { + print('=-=-=-=$json'); final spaceModels = (json['data'] as List) .map((spaceJson) => SpaceModel.fromJson(spaceJson)) .toList(); diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 4e35e84f..acb314d9 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -391,4 +391,8 @@ class Assets { 'assets/icons/uncompleate_process_icon.svg'; static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg'; + static const String arrowForward = + 'assets/icons/arrow_forward.svg'; + static const String arrowDown = + 'assets/icons/arrow_down.svg'; } From a46f69636d1dd213943e3017c621a3492a7c22fc Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 16 Dec 2024 18:19:22 +0300 Subject: [PATCH 012/175] add_user_dialog --- .../users_page/bloc/users_bloc.dart | 87 ++++++++----------- .../users_page/bloc/users_event.dart | 11 +++ .../users_page/view/add_user_dialog.dart | 18 ++-- .../users_page/view/spaces_access_view.dart | 11 +-- 4 files changed, 64 insertions(+), 63 deletions(-) diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart index 0d153804..72c85bad 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -7,7 +7,6 @@ import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_nod import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; - class UsersBloc extends Bloc { UsersBloc() : super(UsersInitial()) { on(_getUsers); @@ -15,6 +14,7 @@ class UsersBloc extends Bloc { on(isCompleteBasicsFun); on(_onLoadCommunityAndSpaces); on(searchTreeNode); + on(isCompleteSpacesFun); } List users = []; @@ -62,7 +62,6 @@ class UsersBloc extends Bloc { void _changeUserStatus(ChangeUserStatus event, Emitter emit) { try { - // Update the user's status users = users.map((user) { if (user.id == event.userId) { return RolesUserModel( @@ -111,21 +110,27 @@ class UsersBloc extends Bloc { return isCompleteBasics; } - isCompleteSpacesFun(CheckStepStatus event, Emitter emit) { + void isCompleteSpacesFun( + CheckSpacesStepStatus event, Emitter emit) { emit(UsersLoadingState()); - isCompleteSpaces = false; + + try { + List selectedIds = + getSelectedIds(updatedCommunities); + isCompleteSpaces = selectedIds.isNotEmpty; + } catch (e) { + emit(ErrorState('Error while retrieving selected IDs: $e')); + return; + } + emit(ChangeStatusSteps()); - print('isCompleteBasics==$isCompleteSpaces'); - return isCompleteSpaces; } bool checkRolePermissions() { return true; } - bool checkSpaces() { - return true; - } + Future> _fetchSpacesForCommunity( String communityUuid) async { @@ -134,83 +139,60 @@ class UsersBloc extends Bloc { List updatedCommunities = []; List spacesNodes = []; + _onLoadCommunityAndSpaces( - LoadCommunityAndSpacesEvent event, - Emitter emit, - ) async { - emit(UsersLoadingState()); // Emit loading state + LoadCommunityAndSpacesEvent event, Emitter emit) async { try { - // Fetch the list of communities + emit(UsersLoadingState()); List communities = await CommunitySpaceManagementApi().fetchCommunities(); - - // Fetch spaces and create TreeNodes for each community updatedCommunities = await Future.wait( communities.map((community) async { - // Fetch spaces for the current community List spaces = await _fetchSpacesForCommunity(community.uuid); - - // Recursively build the tree structure spacesNodes = _buildTreeNodes(spaces); - - // Return a TreeNode for the community, with spaces as its children return TreeNode( uuid: community.uuid, title: community.name, children: spacesNodes, - isChecked: false, // Initial state; can be updated later + isChecked: false, isHighlighted: false, - isExpanded: true, // Default to expanded for better UX + isExpanded: true, ); }).toList(), ); - - // Emit the final state with the structured tree emit(ChangeStatusSteps()); - - return updatedCommunities; // Return the structured data if needed + return updatedCommunities; } catch (e) { - // Emit error state in case of failure emit(ErrorState('Error loading communities and spaces: $e')); } } -// Helper function to recursively build tree nodes List _buildTreeNodes(List spaces) { return spaces.map((space) { - // If the space has children, recursively build nodes for them List childNodes = - space.children != null ? _buildTreeNodes(space.children!) : []; - - // Create a TreeNode for the current space + space.children != null ? _buildTreeNodes(space.children) : []; return TreeNode( uuid: space.uuid!, title: space.name, isChecked: false, isHighlighted: false, - isExpanded: childNodes.isNotEmpty, // Expand if there are children + isExpanded: childNodes.isNotEmpty, children: childNodes, ); }).toList(); } void searchTreeNode(SearchAnode event, Emitter emit) { - emit(UsersLoadingState()); // Emit loading state - - // Clear all highlights if the search term is empty + emit(UsersLoadingState()); if (event.searchTerm!.isEmpty) { _clearHighlights(updatedCommunities); } else { - // Perform the search and update the highlights _searchAndHighlightNodes(updatedCommunities, event.searchTerm!); } - - // Emit the updated state after processing all nodes emit(ChangeStatusSteps()); } -// Helper function to clear all highlights in the tree void _clearHighlights(List nodes) { for (var node in nodes) { node.isHighlighted = false; @@ -220,25 +202,32 @@ class UsersBloc extends Bloc { } } -// Helper function to search and highlight nodes recursively bool _searchAndHighlightNodes(List nodes, String searchTerm) { bool anyMatch = false; for (var node in nodes) { - // Check if this node matches the search term bool isMatch = node.title.toLowerCase().contains(searchTerm.toLowerCase()); - - // Recursively check children for matches bool childMatch = _searchAndHighlightNodes(node.children, searchTerm); - - // Highlight this node if it matches or any of its children match node.isHighlighted = isMatch || childMatch; - // Update if any matches were found in this branch anyMatch = anyMatch || node.isHighlighted; } - return anyMatch; } + + List selectedIds = []; + List getSelectedIds(List nodes) { + List selectedIds = []; + for (var node in nodes) { + if (node.isChecked) { + selectedIds.add(node.uuid); + } + if (node.children.isNotEmpty) { + selectedIds.addAll(getSelectedIds(node.children)); + } + } + return selectedIds; + } + } diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart index 77890673..f1675b08 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart @@ -10,6 +10,11 @@ class GetUsers extends UsersEvent { @override List get props => []; } +class CheckSpacesStepStatus extends UsersEvent { + const CheckSpacesStepStatus(); + @override + List get props => []; +} class LoadCommunityAndSpacesEvent extends UsersEvent { const LoadCommunityAndSpacesEvent(); @@ -49,3 +54,9 @@ class SearchAnode extends UsersEvent { @override List get props => [nodes, searchTerm]; } +class SelecteId extends UsersEvent { + List? nodes; + SelecteId({this.nodes,}); + @override + List get props => [nodes]; +} diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart index 21e92727..895f4825 100644 --- a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart @@ -23,7 +23,8 @@ class _AddNewUserDialogState extends State { @override Widget build(BuildContext context) { return BlocProvider( - create: (BuildContext context) => UsersBloc()..add(LoadCommunityAndSpacesEvent()), + create: (BuildContext context) => + UsersBloc()..add(LoadCommunityAndSpacesEvent()), child: BlocConsumer( listener: (context, state) {}, builder: (context, state) { @@ -121,11 +122,11 @@ class _AddNewUserDialogState extends State { ), ], ), - ), - ], - ), - )); - })); + ), + ], + ), + )); + })); } Widget _getFormContent() { @@ -162,7 +163,9 @@ class _AddNewUserDialogState extends State { ? Assets.currentProcessIcon : bloc.isCompleteSpaces == false ? Assets.wrongProcessIcon - : Assets.uncomplete_ProcessIcon, + : bloc.isCompleteSpaces == true + ? Assets.completeProcessIcon + : Assets.uncomplete_ProcessIcon, width: 25, height: 25, ), @@ -263,6 +266,7 @@ class _AddNewUserDialogState extends State { onTap: () { setState(() { currentStep = step; + bloc.add(const CheckSpacesStepStatus()); }); }, child: Column( diff --git a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart index 5dff9b0a..4bc330b2 100644 --- a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart @@ -125,6 +125,7 @@ class SpacesAccessView extends StatelessWidget { } // ignore: must_be_immutable + class TreeView extends StatefulWidget { UsersBloc? bloc; TreeView({super.key, this.bloc}); @@ -155,6 +156,8 @@ class _TreeViewState extends State { node.isChecked = !node.isChecked; _updateChildrenCheckStatus(node, node.isChecked); _updateParentCheckStatus(node); + // widget.bloc!.add( + // SelecteId(nodes: widget.bloc!.updatedCommunities)); }); }, child: Image.asset( @@ -210,7 +213,6 @@ class _TreeViewState extends State { ); } - // Determine the appropriate image based on the check state String _getCheckBoxImage(TreeNode node) { if (node.children.isEmpty) { return node.isChecked ? Assets.CheckBoxChecked : Assets.emptyBox; @@ -224,7 +226,6 @@ class _TreeViewState extends State { } } - // Helper to determine if all children are checked bool _areAllChildrenChecked(TreeNode node) { return node.children.isNotEmpty && node.children.every((child) => @@ -232,7 +233,6 @@ class _TreeViewState extends State { (child.children.isEmpty || _areAllChildrenChecked(child))); } - // Helper to determine if some children are checked bool _areSomeChildrenChecked(TreeNode node) { return node.children.isNotEmpty && node.children.any((child) => @@ -240,7 +240,6 @@ class _TreeViewState extends State { (child.children.isNotEmpty && _areSomeChildrenChecked(child))); } - // Update the checkbox state for all children void _updateChildrenCheckStatus(TreeNode node, bool isChecked) { for (var child in node.children) { child.isChecked = isChecked; @@ -248,18 +247,16 @@ class _TreeViewState extends State { } } - // Update the checkbox state for parent nodes void _updateParentCheckStatus(TreeNode node) { TreeNode? parent = _findParent(widget.bloc!.updatedCommunities, node); if (parent != null) { setState(() { parent.isChecked = _areAllChildrenChecked(parent); - _updateParentCheckStatus(parent); // Recursively update ancestors + _updateParentCheckStatus(parent); }); } } - // Helper to find a node's parent TreeNode? _findParent(List nodes, TreeNode target) { for (var node in nodes) { if (node.children.contains(target)) { From c31f1262a2ee6e51ec7b4c15f49d4c5f9c441969 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 16 Dec 2024 18:59:56 +0300 Subject: [PATCH 013/175] delete_dialog --- .../users_page/bloc/users_bloc.dart | 18 ++---- .../users_page/view/delete_user_dialog.dart | 63 +++++++++++++++++++ 2 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart index 72c85bad..ad5e49ce 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_nod import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; + class UsersBloc extends Bloc { UsersBloc() : super(UsersInitial()) { on(_getUsers); @@ -103,9 +104,7 @@ class UsersBloc extends Bloc { emit(UsersLoadingState()); isCompleteBasics = firstNameController.text.isNotEmpty && lastNameController.text.isNotEmpty && - emailController.text.isNotEmpty && - phoneController.text.isNotEmpty && - jobTitleController.text.isNotEmpty; + emailController.text.isNotEmpty; emit(ChangeStatusSteps()); return isCompleteBasics; } @@ -115,8 +114,7 @@ class UsersBloc extends Bloc { emit(UsersLoadingState()); try { - List selectedIds = - getSelectedIds(updatedCommunities); + List selectedIds = getSelectedIds(updatedCommunities); isCompleteSpaces = selectedIds.isNotEmpty; } catch (e) { emit(ErrorState('Error while retrieving selected IDs: $e')); @@ -130,8 +128,6 @@ class UsersBloc extends Bloc { return true; } - - Future> _fetchSpacesForCommunity( String communityUuid) async { return await CommunitySpaceManagementApi().getSpaceHierarchy(communityUuid); @@ -204,7 +200,6 @@ class UsersBloc extends Bloc { bool _searchAndHighlightNodes(List nodes, String searchTerm) { bool anyMatch = false; - for (var node in nodes) { bool isMatch = node.title.toLowerCase().contains(searchTerm.toLowerCase()); @@ -218,16 +213,15 @@ class UsersBloc extends Bloc { List selectedIds = []; List getSelectedIds(List nodes) { - List selectedIds = []; + List selectedIds = []; for (var node in nodes) { if (node.isChecked) { - selectedIds.add(node.uuid); + selectedIds.add(node.uuid); } if (node.children.isNotEmpty) { selectedIds.addAll(getSelectedIds(node.children)); } } - return selectedIds; + return selectedIds; } - } diff --git a/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart new file mode 100644 index 00000000..090d4fe3 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class AddNewUserDialog extends StatefulWidget { + const AddNewUserDialog({super.key}); + + @override + _AddNewUserDialogState createState() => _AddNewUserDialogState(); +} + +class _AddNewUserDialogState extends State { + int currentStep = 1; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => UsersBloc(), + child: BlocConsumer( + listener: (context, state) {}, + builder: (context, state) { + final _blocRole = BlocProvider.of(context); + + return Dialog( + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(20))), + child: const Column( + children: [ + Padding( + padding: EdgeInsets.all(8.0), + child: SizedBox( + child: Text( + "Delete User", + style: TextStyle( + color: ColorsManager.red, + fontSize: 18, + fontWeight: FontWeight.bold), + ), + ), + ), + Divider(), + Expanded( + child: Text( + "Are you sure you want to delete this user?", + textAlign: TextAlign.center, + )), + Row( + children: [ + Expanded(child: Text('Cancel')), + Expanded(child: Text('Delete')), + ], + ) + ], + ), + )); + })); + } +} From c362973c941eab96153f4b3c74facc1b1e0090d0 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Tue, 17 Dec 2024 01:55:08 +0300 Subject: [PATCH 014/175] Hide unused widgets --- .../view/access_management.dart | 73 +++--- lib/pages/auth/bloc/auth_bloc.dart | 40 +-- lib/pages/auth/view/login_web_page.dart | 4 +- .../ac/view/ac_device_batch_control.dart | 14 +- .../view/ceiling_sensor_batch_control.dart | 4 +- .../view/curtain_batch_status_view.dart | 11 +- .../view/door_lock_batch_control_view.dart | 27 +- .../view/garage_door_batch_control_view.dart | 19 +- .../gateway/view/gateway_batch_control.dart | 23 +- .../view/main_door_sensor_batch_view.dart | 24 +- .../one_gang_glass_batch_control_view.dart | 19 +- .../view/wall_light_batch_control.dart | 22 +- .../view/power_clamp_batch_control_view.dart | 29 +-- .../shared/batch_control/firmware_update.dart | 244 +++++++++--------- .../sos/view/sos_batch_control_view.dart | 19 +- ..._gang_glass_switch_batch_control_view.dart | 10 +- .../view/living_room_batch_controls.dart | 23 +- ..._gang_glass_switch_batch_control_view.dart | 14 +- .../view/wall_light_batch_control.dart | 16 +- .../view/wall_sensor_batch_control.dart | 4 +- .../view/water_heater_batch_control.dart | 20 +- .../view/water_leak_batch_control_view.dart | 29 +-- lib/pages/home/bloc/home_bloc.dart | 70 ++--- lib/pages/home/view/home_page_web.dart | 2 +- lib/utils/user_drop_down_menu.dart | 46 ++-- 25 files changed, 378 insertions(+), 428 deletions(-) diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index bed27eea..9fe3a722 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -11,7 +11,7 @@ import 'package:syncrow_web/pages/common/filter/filter_widget.dart'; import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; +// import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/app_enum.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -27,8 +27,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { final isLargeScreen = isLargeScreenSize(context); final isSmallScreen = isSmallScreenSize(context); final isHalfMediumScreen = isHafMediumScreenSize(context); - final padding = - isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15); + final padding = isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15); return WebScaffold( enableMenuSidebar: false, @@ -40,8 +39,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { ), rightBody: const NavigateHomeGridView(), scaffoldBody: BlocProvider( - create: (BuildContext context) => - AccessBloc()..add(FetchTableData()), + create: (BuildContext context) => AccessBloc()..add(FetchTableData()), child: BlocConsumer( listener: (context, state) {}, builder: (context, state) { @@ -95,14 +93,11 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { return [ item.passwordName, item.passwordType.value, - accessBloc - .timestampToDate(item.effectiveTime), - accessBloc - .timestampToDate(item.invalidTime), + accessBloc.timestampToDate(item.effectiveTime), + accessBloc.timestampToDate(item.invalidTime), item.deviceName.toString(), item.authorizerEmail.toString(), - accessBloc - .timestampToDate(item.invalidTime), + accessBloc.timestampToDate(item.invalidTime), item.passwordStatus.value, ]; }).toList(), @@ -113,8 +108,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { }))); } - Wrap _buildVisitorAdminPasswords( - BuildContext context, AccessBloc accessBloc) { + Wrap _buildVisitorAdminPasswords(BuildContext context, AccessBloc accessBloc) { return Wrap( spacing: 10, runSpacing: 10, @@ -140,23 +134,22 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { borderRadius: 8, child: Text( 'Create Visitor Password ', - style: context.textTheme.titleSmall! - .copyWith(color: Colors.white, fontSize: 12), - )), - ), - Container( - width: 133, - height: 42, - decoration: containerDecoration, - child: DefaultButton( - borderRadius: 8, - backgroundColor: ColorsManager.whiteColors, - child: Text( - 'Admin Password', - style: context.textTheme.titleSmall! - .copyWith(color: Colors.black, fontSize: 12), + style: context.textTheme.titleSmall!.copyWith(color: Colors.white, fontSize: 12), )), ), + // Container( + // width: 133, + // height: 42, + // decoration: containerDecoration, + // child: DefaultButton( + // borderRadius: 8, + // backgroundColor: ColorsManager.whiteColors, + // child: Text( + // 'Admin Password', + // style: context.textTheme.titleSmall! + // .copyWith(color: Colors.black, fontSize: 12), + // )), + // ), ], ); } @@ -179,10 +172,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { description: '', onSubmitted: (value) { accessBloc.add(FilterDataEvent( - emailAuthorizer: - accessBloc.emailAuthorizer.text.toLowerCase(), - selectedTabIndex: - BlocProvider.of(context).selectedIndex, + emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(), + selectedTabIndex: BlocProvider.of(context).selectedIndex, passwordName: accessBloc.passwordName.text.toLowerCase(), startTime: accessBloc.effectiveTimeTimeStamp, endTime: accessBloc.expirationTimeTimeStamp)); @@ -200,10 +191,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { description: '', onSubmitted: (value) { accessBloc.add(FilterDataEvent( - emailAuthorizer: - accessBloc.emailAuthorizer.text.toLowerCase(), - selectedTabIndex: - BlocProvider.of(context).selectedIndex, + emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(), + selectedTabIndex: BlocProvider.of(context).selectedIndex, passwordName: accessBloc.passwordName.text.toLowerCase(), startTime: accessBloc.effectiveTimeTimeStamp, endTime: accessBloc.expirationTimeTimeStamp)); @@ -232,8 +221,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { onSearch: () { accessBloc.add(FilterDataEvent( emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(), - selectedTabIndex: - BlocProvider.of(context).selectedIndex, + selectedTabIndex: BlocProvider.of(context).selectedIndex, passwordName: accessBloc.passwordName.text.toLowerCase(), startTime: accessBloc.effectiveTimeTimeStamp, endTime: accessBloc.expirationTimeTimeStamp)); @@ -261,10 +249,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { description: '', onSubmitted: (value) { accessBloc.add(FilterDataEvent( - emailAuthorizer: - accessBloc.emailAuthorizer.text.toLowerCase(), - selectedTabIndex: - BlocProvider.of(context).selectedIndex, + emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(), + selectedTabIndex: BlocProvider.of(context).selectedIndex, passwordName: accessBloc.passwordName.text.toLowerCase(), startTime: accessBloc.effectiveTimeTimeStamp, endTime: accessBloc.expirationTimeTimeStamp)); @@ -288,8 +274,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { onSearch: () { accessBloc.add(FilterDataEvent( emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(), - selectedTabIndex: - BlocProvider.of(context).selectedIndex, + selectedTabIndex: BlocProvider.of(context).selectedIndex, passwordName: accessBloc.passwordName.text.toLowerCase(), startTime: accessBloc.effectiveTimeTimeStamp, endTime: accessBloc.expirationTimeTimeStamp)); diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index 60cc2f86..95ecb0ad 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -31,8 +31,7 @@ class AuthBloc extends Bloc { ////////////////////////////// forget password ////////////////////////////////// final TextEditingController forgetEmailController = TextEditingController(); - final TextEditingController forgetPasswordController = - TextEditingController(); + final TextEditingController forgetPasswordController = TextEditingController(); final TextEditingController forgetOtp = TextEditingController(); final forgetFormKey = GlobalKey(); final forgetEmailKey = GlobalKey(); @@ -49,8 +48,7 @@ class AuthBloc extends Bloc { return; } _remainingTime = 1; - add(UpdateTimerEvent( - remainingTime: _remainingTime, isButtonEnabled: false)); + add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); try { forgetEmailValidate = ''; _remainingTime = (await AuthenticationAPI.sendOtp( @@ -86,8 +84,7 @@ class AuthBloc extends Bloc { _timer?.cancel(); add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true)); } else { - add(UpdateTimerEvent( - remainingTime: _remainingTime, isButtonEnabled: false)); + add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); } }); } @@ -97,8 +94,7 @@ class AuthBloc extends Bloc { emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); } - Future changePassword( - ChangePasswordEvent event, Emitter emit) async { + Future changePassword(ChangePasswordEvent event, Emitter emit) async { emit(LoadingForgetState()); try { var response = await AuthenticationAPI.verifyOtp( @@ -114,8 +110,7 @@ class AuthBloc extends Bloc { } } on DioException catch (e) { final errorData = e.response!.data; - String errorMessage = - errorData['error']['message'] ?? 'something went wrong'; + String errorMessage = errorData['error']['message'] ?? 'something went wrong'; validate = errorMessage; emit(AuthInitialState()); } @@ -129,16 +124,14 @@ class AuthBloc extends Bloc { } void _onUpdateTimer(UpdateTimerEvent event, Emitter emit) { - emit(TimerState( - isButtonEnabled: event.isButtonEnabled, - remainingTime: event.remainingTime)); + emit(TimerState(isButtonEnabled: event.isButtonEnabled, remainingTime: event.remainingTime)); } ///////////////////////////////////// login ///////////////////////////////////// final TextEditingController loginEmailController = TextEditingController(); final TextEditingController loginPasswordController = TextEditingController(); final loginFormKey = GlobalKey(); - bool isChecked = false; + bool isChecked = true; bool obscureText = true; String newPassword = ''; String maskedEmail = ''; @@ -163,9 +156,7 @@ class AuthBloc extends Bloc { token = await AuthenticationAPI.loginWithEmail( model: LoginWithEmailModel( - email: event.username, - password: event.password, - regionUuid: event.regionUuid), + email: event.username, password: event.password, regionUuid: event.regionUuid), ); } catch (failure) { validate = 'Invalid Credentials!'; @@ -175,8 +166,7 @@ class AuthBloc extends Bloc { if (token.accessTokenIsNotEmpty) { FlutterSecureStorage storage = const FlutterSecureStorage(); - await storage.write( - key: Token.loginAccessTokenKey, value: token.accessToken); + await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken); const FlutterSecureStorage().write( key: UserModel.userUuidKey, value: Token.decodeToken(token.accessToken)['uuid'].toString()); @@ -334,14 +324,12 @@ class AuthBloc extends Bloc { static Future getTokenAndValidate() async { try { const storage = FlutterSecureStorage(); - final firstLaunch = await SharedPreferencesHelper.readBoolFromSP( - StringsManager.firstLaunch) ?? - true; + final firstLaunch = + await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true; if (firstLaunch) { storage.deleteAll(); } - await SharedPreferencesHelper.saveBoolToSP( - StringsManager.firstLaunch, false); + await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false); final value = await storage.read(key: Token.loginAccessTokenKey) ?? ''; if (value.isEmpty) { return 'Token not found'; @@ -394,9 +382,7 @@ class AuthBloc extends Bloc { final String formattedTime = [ if (days > 0) '${days}d', // Append 'd' for days if (days > 0 || hours > 0) - hours - .toString() - .padLeft(2, '0'), // Show hours if there are days or hours + hours.toString().padLeft(2, '0'), // Show hours if there are days or hours minutes.toString().padLeft(2, '0'), seconds.toString().padLeft(2, '0'), ].join(':'); diff --git a/lib/pages/auth/view/login_web_page.dart b/lib/pages/auth/view/login_web_page.dart index 555f8f4a..303dac76 100644 --- a/lib/pages/auth/view/login_web_page.dart +++ b/lib/pages/auth/view/login_web_page.dart @@ -163,8 +163,8 @@ class _LoginWebPageState extends State with HelperResponsiveLayout _buildPasswordField(context, loginBloc), const SizedBox(height: 20), _buildForgotPassword(context), - const SizedBox(height: 20), - _buildCheckbox(context, loginBloc, size), + // const SizedBox(height: 20), + // _buildCheckbox(context, loginBloc, size), const SizedBox(height: 20.0), _buildSignInButton(context, loginBloc, size), const SizedBox(height: 15.0), diff --git a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart index 2da394c7..3005c1c5 100644 --- a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart @@ -8,7 +8,7 @@ import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/ba import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -26,7 +26,8 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); return BlocProvider( - create: (context) => AcBloc(deviceId: devicesIds.first)..add(AcFetchBatchStatusEvent(devicesIds)), + create: (context) => + AcBloc(deviceId: devicesIds.first)..add(AcFetchBatchStatusEvent(devicesIds)), child: BlocBuilder( builder: (context, state) { if (state is ACStatusLoaded) { @@ -98,7 +99,8 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo ), Text( 'h', - style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor), + style: + context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor), ), Text( '30', @@ -107,7 +109,9 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo fontWeight: FontWeight.bold, ), ), - Text('m', style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor)), + Text('m', + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blackColor)), IconButton( onPressed: () {}, icon: const Icon( @@ -138,7 +142,7 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo )); }, ), - FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5), + // FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5), FactoryResetWidget( callFactoryReset: () { context.read().add(AcFactoryResetEvent( diff --git a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart index b7f9af5d..cf645b6f 100644 --- a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart +++ b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart @@ -6,7 +6,7 @@ import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_e import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_space_type.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart'; @@ -110,7 +110,7 @@ class CeilingSensorBatchControlView extends StatelessWidget with HelperResponsiv ), ), ), - FirmwareUpdateWidget(deviceId: devicesIds.first, version: 4), + // FirmwareUpdateWidget(deviceId: devicesIds.first, version: 4), FactoryResetWidget( callFactoryReset: () { context.read().add( diff --git a/lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart b/lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart index b558c837..7c873e20 100644 --- a/lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart +++ b/lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart @@ -6,11 +6,10 @@ import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dar import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart'; import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class CurtainBatchStatusView extends StatelessWidget - with HelperResponsiveLayout { +class CurtainBatchStatusView extends StatelessWidget with HelperResponsiveLayout { const CurtainBatchStatusView({super.key, required this.devicesIds}); final List devicesIds; @@ -18,8 +17,8 @@ class CurtainBatchStatusView extends StatelessWidget @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => CurtainBloc(deviceId: devicesIds.first) - ..add(CurtainFetchBatchStatus(devicesIds)), + create: (context) => + CurtainBloc(deviceId: devicesIds.first)..add(CurtainFetchBatchStatus(devicesIds)), child: BlocBuilder( builder: (context, state) { if (state is CurtainStatusLoading) { @@ -68,7 +67,7 @@ class CurtainBatchStatusView extends StatelessWidget )); }, ), - FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5), + // FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5), FactoryResetWidget( callFactoryReset: () { context.read().add( diff --git a/lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart b/lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart index abbd48dd..b28737fc 100644 --- a/lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart +++ b/lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart @@ -4,11 +4,10 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_re import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_bloc.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_event.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class DoorLockBatchControlView extends StatelessWidget - with HelperResponsiveLayout { +class DoorLockBatchControlView extends StatelessWidget with HelperResponsiveLayout { const DoorLockBatchControlView({super.key, required this.devicesIds}); final List devicesIds; @@ -18,17 +17,17 @@ class DoorLockBatchControlView extends StatelessWidget return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - SizedBox( - width: 170, - height: 140, - child: FirmwareUpdateWidget( - deviceId: devicesIds.first, - version: 12, - ), - ), - const SizedBox( - width: 12, - ), + // SizedBox( + // width: 170, + // height: 140, + // child: FirmwareUpdateWidget( + // deviceId: devicesIds.first, + // version: 12, + // ), + // ), + // const SizedBox( + // width: 12, + // ), SizedBox( width: 170, height: 140, diff --git a/lib/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart b/lib/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart index 8c8b60cf..9b3159bb 100644 --- a/lib/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart +++ b/lib/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart @@ -6,17 +6,15 @@ import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_ import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class GarageDoorBatchControlView extends StatelessWidget - with HelperResponsiveLayout { +class GarageDoorBatchControlView extends StatelessWidget with HelperResponsiveLayout { final List deviceIds; - const GarageDoorBatchControlView({Key? key, required this.deviceIds}) - : super(key: key); + const GarageDoorBatchControlView({Key? key, required this.deviceIds}) : super(key: key); @override Widget build(BuildContext context) { @@ -39,8 +37,7 @@ class GarageDoorBatchControlView extends StatelessWidget ); } - Widget _buildStatusControls( - BuildContext context, GarageDoorStatusModel status) { + Widget _buildStatusControls(BuildContext context, GarageDoorStatusModel status) { final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); @@ -75,10 +72,10 @@ class GarageDoorBatchControlView extends StatelessWidget ); }, ), - FirmwareUpdateWidget( - deviceId: deviceIds.first, - version: 12, - ), + // FirmwareUpdateWidget( + // deviceId: deviceIds.first, + // version: 12, + // ), FactoryResetWidget( callFactoryReset: () { context.read().add( diff --git a/lib/pages/device_managment/gateway/view/gateway_batch_control.dart b/lib/pages/device_managment/gateway/view/gateway_batch_control.dart index cb85b7d9..f3a08a18 100644 --- a/lib/pages/device_managment/gateway/view/gateway_batch_control.dart +++ b/lib/pages/device_managment/gateway/view/gateway_batch_control.dart @@ -3,11 +3,10 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/gateway/bloc/gate_way_bloc.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class GatewayBatchControlView extends StatelessWidget - with HelperResponsiveLayout { +class GatewayBatchControlView extends StatelessWidget with HelperResponsiveLayout { const GatewayBatchControlView({super.key, required this.gatewayIds}); final List gatewayIds; @@ -24,14 +23,13 @@ class GatewayBatchControlView extends StatelessWidget return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - SizedBox( - width: 170, - height: 140, - child: FirmwareUpdateWidget( - deviceId: gatewayIds.first, version: 2)), - const SizedBox( - width: 12, - ), + // SizedBox( + // width: 170, + // height: 140, + // child: FirmwareUpdateWidget(deviceId: gatewayIds.first, version: 2)), + // const SizedBox( + // width: 12, + // ), SizedBox( width: 170, height: 140, @@ -40,8 +38,7 @@ class GatewayBatchControlView extends StatelessWidget context.read().add( GateWayFactoryReset( deviceId: gatewayIds.first, - factoryReset: - FactoryResetModel(devicesUuid: gatewayIds), + factoryReset: FactoryResetModel(devicesUuid: gatewayIds), ), ); }, diff --git a/lib/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart b/lib/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart index 0cacc0be..7337c9fd 100644 --- a/lib/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart +++ b/lib/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart @@ -4,7 +4,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_re import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart'; import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; class MainDoorSensorBatchView extends StatelessWidget { const MainDoorSensorBatchView({super.key, required this.devicesIds}); @@ -16,17 +16,17 @@ class MainDoorSensorBatchView extends StatelessWidget { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - SizedBox( - width: 170, - height: 140, - child: FirmwareUpdateWidget( - deviceId: devicesIds.first, - version: 12, - ), - ), - const SizedBox( - width: 12, - ), + // SizedBox( + // width: 170, + // height: 140, + // child: FirmwareUpdateWidget( + // deviceId: devicesIds.first, + // version: 12, + // ), + // ), + // const SizedBox( + // width: 12, + // ), SizedBox( width: 170, height: 140, diff --git a/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart index 4239b08e..9b89e876 100644 --- a/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart +++ b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart @@ -4,16 +4,14 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_re import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class OneGangGlassSwitchBatchControlView extends StatelessWidget - with HelperResponsiveLayout { +class OneGangGlassSwitchBatchControlView extends StatelessWidget with HelperResponsiveLayout { final List deviceIds; - const OneGangGlassSwitchBatchControlView( - {required this.deviceIds, super.key}); + const OneGangGlassSwitchBatchControlView({required this.deviceIds, super.key}); @override Widget build(BuildContext context) { @@ -36,8 +34,7 @@ class OneGangGlassSwitchBatchControlView extends StatelessWidget ); } - Widget _buildStatusControls( - BuildContext context, OneGangGlassStatusModel status) { + Widget _buildStatusControls(BuildContext context, OneGangGlassStatusModel status) { final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); @@ -71,10 +68,10 @@ class OneGangGlassSwitchBatchControlView extends StatelessWidget ); }, ), - FirmwareUpdateWidget( - deviceId: deviceIds.first, - version: 12, - ), + // FirmwareUpdateWidget( + // deviceId: deviceIds.first, + // version: 12, + // ), FactoryResetWidget( callFactoryReset: () { context.read().add( diff --git a/lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart b/lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart index e1dabb61..7094b506 100644 --- a/lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart +++ b/lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart @@ -6,12 +6,11 @@ import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_lig import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class WallLightBatchControlView extends StatelessWidget - with HelperResponsiveLayout { +class WallLightBatchControlView extends StatelessWidget with HelperResponsiveLayout { const WallLightBatchControlView({super.key, required this.deviceIds}); final List deviceIds; @@ -27,8 +26,7 @@ class WallLightBatchControlView extends StatelessWidget return const Center(child: CircularProgressIndicator()); } else if (state is WallLightSwitchStatusLoaded) { return _buildStatusControls(context, state.status); - } else if (state is WallLightSwitchError || - state is WallLightSwitchControlError) { + } else if (state is WallLightSwitchError || state is WallLightSwitchControlError) { return const Center(child: Text('Error fetching status')); } else { return const Center(child: CircularProgressIndicator()); @@ -38,8 +36,7 @@ class WallLightBatchControlView extends StatelessWidget ); } - Widget _buildStatusControls( - BuildContext context, WallLightStatusModel status) { + Widget _buildStatusControls(BuildContext context, WallLightStatusModel status) { final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); @@ -74,15 +71,14 @@ class WallLightBatchControlView extends StatelessWidget ); }, ), - FirmwareUpdateWidget( - deviceId: deviceIds.first, - version: 12, - ), + // FirmwareUpdateWidget( + // deviceId: deviceIds.first, + // version: 12, + // ), FactoryResetWidget( callFactoryReset: () { context.read().add(WallLightFactoryReset( - deviceId: status.uuid, - factoryReset: FactoryResetModel(devicesUuid: deviceIds))); + deviceId: status.uuid, factoryReset: FactoryResetModel(devicesUuid: deviceIds))); }, ), ], diff --git a/lib/pages/device_managment/power_clamp/view/power_clamp_batch_control_view.dart b/lib/pages/device_managment/power_clamp/view/power_clamp_batch_control_view.dart index c0244845..7f6a4b64 100644 --- a/lib/pages/device_managment/power_clamp/view/power_clamp_batch_control_view.dart +++ b/lib/pages/device_managment/power_clamp/view/power_clamp_batch_control_view.dart @@ -6,21 +6,19 @@ import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_ import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_state.dart'; import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_batch_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class PowerClampBatchControlView extends StatelessWidget - with HelperResponsiveLayout { +class PowerClampBatchControlView extends StatelessWidget with HelperResponsiveLayout { final List deviceIds; - const PowerClampBatchControlView({Key? key, required this.deviceIds}) - : super(key: key); + const PowerClampBatchControlView({Key? key, required this.deviceIds}) : super(key: key); @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => SmartPowerBloc(deviceId: deviceIds.first) - ..add(SmartPowerFetchBatchEvent(deviceIds)), + create: (context) => + SmartPowerBloc(deviceId: deviceIds.first)..add(SmartPowerFetchBatchEvent(deviceIds)), child: BlocBuilder( builder: (context, state) { if (state is SmartPowerLoading) { @@ -37,18 +35,17 @@ class PowerClampBatchControlView extends StatelessWidget ); } - Widget _buildStatusControls( - BuildContext context, PowerClampBatchModel status) { + Widget _buildStatusControls(BuildContext context, PowerClampBatchModel status) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - SizedBox( - width: 170, - // height: 140, - child: FirmwareUpdateWidget(deviceId: deviceIds.first, version: 2)), - const SizedBox( - width: 12, - ), + // SizedBox( + // width: 170, + // // height: 140, + // child: FirmwareUpdateWidget(deviceId: deviceIds.first, version: 2)), + // const SizedBox( + // width: 12, + // ), SizedBox( width: 170, height: 140, diff --git a/lib/pages/device_managment/shared/batch_control/firmware_update.dart b/lib/pages/device_managment/shared/batch_control/firmware_update.dart index e99ee948..8270fa36 100644 --- a/lib/pages/device_managment/shared/batch_control/firmware_update.dart +++ b/lib/pages/device_managment/shared/batch_control/firmware_update.dart @@ -1,128 +1,128 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/pages/common/buttons/default_button.dart'; -import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.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:flutter/material.dart'; +// import 'package:flutter_svg/flutter_svg.dart'; +// import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +// import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.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'; -class FirmwareUpdateWidget extends StatefulWidget { - const FirmwareUpdateWidget({super.key, required this.deviceId, required this.version}); +// class FirmwareUpdateWidget extends StatefulWidget { +// const FirmwareUpdateWidget({super.key, required this.deviceId, required this.version}); - final String deviceId; - final int version; +// final String deviceId; +// final int version; - @override - State createState() => _FirmwareUpdateWidgetState(); -} +// @override +// State createState() => _FirmwareUpdateWidgetState(); +// } -class _FirmwareUpdateWidgetState extends State { - bool _showConfirmation = false; +// class _FirmwareUpdateWidgetState extends State { +// bool _showConfirmation = false; - void _toggleConfirmation() { - setState(() { - _showConfirmation = !_showConfirmation; - }); - } +// void _toggleConfirmation() { +// setState(() { +// _showConfirmation = !_showConfirmation; +// }); +// } - @override - Widget build(BuildContext context) { - return DeviceControlsContainer( - child: _showConfirmation - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - children: [ - Text( - 'Firmware Update', - style: context.textTheme.titleMedium!.copyWith( - fontWeight: FontWeight.bold, - color: ColorsManager.blackColor, - ), - ), - Text( - 'Are you sure?', - style: context.textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor, - ), - ), - ], - ), - Row( - children: [ - Flexible( - child: DefaultButton( - height: 20, - elevation: 0, - padding: 0, - onPressed: _toggleConfirmation, - backgroundColor: ColorsManager.greyColor, - child: Text( - 'Cancel', - style: context.textTheme.bodyMedium!.copyWith( - color: ColorsManager.blackColor, - fontWeight: FontWeight.w400, - fontSize: 12, - ), - ), - ), - ), - const SizedBox(width: 8), - Flexible( - child: DefaultButton( - height: 20, - elevation: 0, - padding: 0, - onPressed: () { - _toggleConfirmation(); - }, - backgroundColor: ColorsManager.primaryColor, - child: Text( - 'Update', - style: context.textTheme.bodyMedium!.copyWith( - color: ColorsManager.whiteColors, - fontWeight: FontWeight.w400, - fontSize: 12, - ), - ), - ), - ), - ], - ), - ], - ) - : GestureDetector( - onTap: _toggleConfirmation, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ClipOval( - child: Container( - color: ColorsManager.whiteColors, - height: 60, - width: 60, - child: Padding( - padding: const EdgeInsets.all(12.0), - child: SvgPicture.asset( - Assets.firmware, - fit: BoxFit.cover, - ), - ), - ), - ), - Text( - 'Firmware Update', - style: context.textTheme.titleMedium!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.blackColor, - ), - ), - ], - ), - ), - ); - } -} +// @override +// Widget build(BuildContext context) { +// return DeviceControlsContainer( +// child: _showConfirmation +// ? Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// Column( +// children: [ +// Text( +// 'Firmware Update', +// style: context.textTheme.titleMedium!.copyWith( +// fontWeight: FontWeight.bold, +// color: ColorsManager.blackColor, +// ), +// ), +// Text( +// 'Are you sure?', +// style: context.textTheme.bodySmall!.copyWith( +// color: ColorsManager.grayColor, +// ), +// ), +// ], +// ), +// Row( +// children: [ +// Flexible( +// child: DefaultButton( +// height: 20, +// elevation: 0, +// padding: 0, +// onPressed: _toggleConfirmation, +// backgroundColor: ColorsManager.greyColor, +// child: Text( +// 'Cancel', +// style: context.textTheme.bodyMedium!.copyWith( +// color: ColorsManager.blackColor, +// fontWeight: FontWeight.w400, +// fontSize: 12, +// ), +// ), +// ), +// ), +// const SizedBox(width: 8), +// Flexible( +// child: DefaultButton( +// height: 20, +// elevation: 0, +// padding: 0, +// onPressed: () { +// _toggleConfirmation(); +// }, +// backgroundColor: ColorsManager.primaryColor, +// child: Text( +// 'Update', +// style: context.textTheme.bodyMedium!.copyWith( +// color: ColorsManager.whiteColors, +// fontWeight: FontWeight.w400, +// fontSize: 12, +// ), +// ), +// ), +// ), +// ], +// ), +// ], +// ) +// : GestureDetector( +// onTap: _toggleConfirmation, +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// ClipOval( +// child: Container( +// color: ColorsManager.whiteColors, +// height: 60, +// width: 60, +// child: Padding( +// padding: const EdgeInsets.all(12.0), +// child: SvgPicture.asset( +// Assets.firmware, +// fit: BoxFit.cover, +// ), +// ), +// ), +// ), +// Text( +// 'Firmware Update', +// style: context.textTheme.titleMedium!.copyWith( +// fontWeight: FontWeight.w400, +// color: ColorsManager.blackColor, +// ), +// ), +// ], +// ), +// ), +// ); +// } +// } diff --git a/lib/pages/device_managment/sos/view/sos_batch_control_view.dart b/lib/pages/device_managment/sos/view/sos_batch_control_view.dart index ec15b7d6..bc66d69f 100644 --- a/lib/pages/device_managment/sos/view/sos_batch_control_view.dart +++ b/lib/pages/device_managment/sos/view/sos_batch_control_view.dart @@ -18,20 +18,21 @@ class SOSBatchControlView extends StatelessWidget { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - SizedBox( - width: 170, - // height: 140, - child: FirmwareUpdateWidget(deviceId: deviceIds.first, version: 2)), - const SizedBox( - width: 12, - ), + // SizedBox( + // width: 170, + // // height: 140, + // child: FirmwareUpdateWidget(deviceId: deviceIds.first, version: 2)), + // const SizedBox( + // width: 12, + // ), SizedBox( width: 170, height: 140, child: FactoryResetWidget( callFactoryReset: () { - context.read().add( - SosFactoryReset(deviceId: deviceIds.first, factoryReset: FactoryResetModel(devicesUuid: deviceIds))); + context.read().add(SosFactoryReset( + deviceId: deviceIds.first, + factoryReset: FactoryResetModel(devicesUuid: deviceIds))); }, ), ), diff --git a/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_batch_control_view.dart b/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_batch_control_view.dart index 4d1bb91c..071d6ca0 100644 --- a/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_batch_control_view.dart +++ b/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_batch_control_view.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/models/three_gang_glass_switch.dart'; @@ -98,10 +98,10 @@ class ThreeGangGlassSwitchBatchControlView extends StatelessWidget with HelperRe ); }, ), - FirmwareUpdateWidget( - deviceId: deviceIds.first, - version: 12, // adjust the version according to your requirement - ), + // FirmwareUpdateWidget( + // deviceId: deviceIds.first, + // version: 12, // adjust the version according to your requirement + // ), FactoryResetWidget( callFactoryReset: () { context.read().add( diff --git a/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart b/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart index 0d82c515..97c25287 100644 --- a/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart +++ b/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart @@ -2,14 +2,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class LivingRoomBatchControlsView extends StatelessWidget - with HelperResponsiveLayout { +class LivingRoomBatchControlsView extends StatelessWidget with HelperResponsiveLayout { const LivingRoomBatchControlsView({super.key, required this.deviceIds}); final List deviceIds; @@ -17,16 +16,15 @@ class LivingRoomBatchControlsView extends StatelessWidget @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => LivingRoomBloc(deviceId: deviceIds.first) - ..add(LivingRoomFetchBatchEvent(deviceIds)), + create: (context) => + LivingRoomBloc(deviceId: deviceIds.first)..add(LivingRoomFetchBatchEvent(deviceIds)), child: BlocBuilder( builder: (context, state) { if (state is LivingRoomDeviceStatusLoading) { return const Center(child: CircularProgressIndicator()); } else if (state is LivingRoomDeviceStatusLoaded) { return _buildStatusControls(context, state.status); - } else if (state is LivingRoomDeviceManagementError || - state is LivingRoomControlError) { + } else if (state is LivingRoomDeviceManagementError || state is LivingRoomControlError) { return const Center(child: Text('Error fetching status')); } else { return const Center(child: CircularProgressIndicator()); @@ -36,8 +34,7 @@ class LivingRoomBatchControlsView extends StatelessWidget ); } - Widget _buildStatusControls( - BuildContext context, LivingRoomStatusModel status) { + Widget _buildStatusControls(BuildContext context, LivingRoomStatusModel status) { final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); @@ -102,10 +99,10 @@ class LivingRoomBatchControlsView extends StatelessWidget ); }, ), - FirmwareUpdateWidget( - deviceId: deviceIds.first, - version: 12, - ), + // FirmwareUpdateWidget( + // deviceId: deviceIds.first, + // version: 12, + // ), FactoryResetWidget(callFactoryReset: () { context.read().add( LivingRoomFactoryResetEvent( diff --git a/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_batch_control_view.dart b/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_batch_control_view.dart index d0288ca3..c84c1d07 100644 --- a/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_batch_control_view.dart +++ b/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_batch_control_view.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart'; @@ -16,8 +16,8 @@ class TwoGangGlassSwitchBatchControlView extends StatelessWidget with HelperResp @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => - TwoGangGlassSwitchBloc(deviceId: deviceIds.first)..add(TwoGangGlassSwitchFetchBatchStatusEvent(deviceIds)), + create: (context) => TwoGangGlassSwitchBloc(deviceId: deviceIds.first) + ..add(TwoGangGlassSwitchFetchBatchStatusEvent(deviceIds)), child: BlocBuilder( builder: (context, state) { if (state is TwoGangGlassSwitchLoading) { @@ -83,10 +83,10 @@ class TwoGangGlassSwitchBatchControlView extends StatelessWidget with HelperResp ); }, ), - FirmwareUpdateWidget( - deviceId: deviceIds.first, - version: 12, // adjust the version according to your requirement - ), + // FirmwareUpdateWidget( + // deviceId: deviceIds.first, + // version: 12, // adjust the version according to your requirement + // ), FactoryResetWidget( callFactoryReset: () { context.read().add( diff --git a/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart b/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart index 52900155..b3a39287 100644 --- a/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart +++ b/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart'; @@ -10,8 +10,7 @@ import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class TwoGangBatchControlView extends StatelessWidget - with HelperResponsiveLayout { +class TwoGangBatchControlView extends StatelessWidget with HelperResponsiveLayout { const TwoGangBatchControlView({super.key, required this.deviceIds}); final List deviceIds; @@ -27,8 +26,7 @@ class TwoGangBatchControlView extends StatelessWidget return const Center(child: CircularProgressIndicator()); } else if (state is TwoGangSwitchStatusLoaded) { return _buildStatusControls(context, state.status); - } else if (state is TwoGangSwitchError || - state is TwoGangSwitchControlError) { + } else if (state is TwoGangSwitchError || state is TwoGangSwitchControlError) { return const Center(child: Text('Error fetching status')); } else { return const Center(child: CircularProgressIndicator()); @@ -84,10 +82,10 @@ class TwoGangBatchControlView extends StatelessWidget )); }, ), - FirmwareUpdateWidget( - deviceId: deviceIds.first, - version: 12, - ), + // FirmwareUpdateWidget( + // deviceId: deviceIds.first, + // version: 12, + // ), FactoryResetWidget(callFactoryReset: () { context.read().add( TwoGangFactoryReset( diff --git a/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart b/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart index 66ff67aa..27169f0e 100644 --- a/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart +++ b/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_bloc.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_event.dart'; @@ -113,7 +113,7 @@ class WallSensorBatchControlView extends StatelessWidget with HelperResponsiveLa ), ), ), - FirmwareUpdateWidget(deviceId: devicesIds.first, version: 2), + // FirmwareUpdateWidget(deviceId: devicesIds.first, version: 2), FactoryResetWidget( callFactoryReset: () { context.read().add( diff --git a/lib/pages/device_managment/water_heater/view/water_heater_batch_control.dart b/lib/pages/device_managment/water_heater/view/water_heater_batch_control.dart index cc62adfd..aaab5271 100644 --- a/lib/pages/device_managment/water_heater/view/water_heater_batch_control.dart +++ b/lib/pages/device_managment/water_heater/view/water_heater_batch_control.dart @@ -2,15 +2,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class WaterHEaterBatchControlView extends StatelessWidget - with HelperResponsiveLayout { +class WaterHEaterBatchControlView extends StatelessWidget with HelperResponsiveLayout { const WaterHEaterBatchControlView({super.key, required this.deviceIds}); final List deviceIds; @@ -18,8 +17,8 @@ class WaterHEaterBatchControlView extends StatelessWidget @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => WaterHeaterBloc() - ..add(FetchWaterHeaterBatchStatusEvent(devicesUuid: deviceIds)), + create: (context) => + WaterHeaterBloc()..add(FetchWaterHeaterBatchStatusEvent(devicesUuid: deviceIds)), child: BlocBuilder( builder: (context, state) { if (state is WaterHeaterLoadingState) { @@ -36,8 +35,7 @@ class WaterHEaterBatchControlView extends StatelessWidget ); } - Widget _buildStatusControls( - BuildContext context, WaterHeaterStatusModel status) { + Widget _buildStatusControls(BuildContext context, WaterHeaterStatusModel status) { final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); @@ -73,10 +71,10 @@ class WaterHEaterBatchControlView extends StatelessWidget ); }, ), - FirmwareUpdateWidget( - deviceId: deviceIds.first, - version: 12, - ), + // FirmwareUpdateWidget( + // deviceId: deviceIds.first, + // version: 12, + // ), FactoryResetWidget( callFactoryReset: () {}, ), diff --git a/lib/pages/device_managment/water_leak/view/water_leak_batch_control_view.dart b/lib/pages/device_managment/water_leak/view/water_leak_batch_control_view.dart index 9d2c030f..1eb795e5 100644 --- a/lib/pages/device_managment/water_leak/view/water_leak_batch_control_view.dart +++ b/lib/pages/device_managment/water_leak/view/water_leak_batch_control_view.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/water_leak/bloc/water_leak_bloc.dart'; import 'package:syncrow_web/pages/device_managment/water_leak/bloc/water_leak_event.dart'; @@ -10,18 +10,16 @@ import 'package:syncrow_web/pages/device_managment/water_leak/bloc/water_leak_st import 'package:syncrow_web/pages/device_managment/water_leak/model/water_leak_status_model.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class WaterLeakBatchControlView extends StatelessWidget - with HelperResponsiveLayout { +class WaterLeakBatchControlView extends StatelessWidget with HelperResponsiveLayout { final List deviceIds; - const WaterLeakBatchControlView({Key? key, required this.deviceIds}) - : super(key: key); + const WaterLeakBatchControlView({Key? key, required this.deviceIds}) : super(key: key); @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => WaterLeakBloc(deviceIds.first) - ..add(FetchWaterLeakBatchStatusEvent(deviceIds)), + create: (context) => + WaterLeakBloc(deviceIds.first)..add(FetchWaterLeakBatchStatusEvent(deviceIds)), child: BlocBuilder( builder: (context, state) { if (state is WaterLeakLoadingState) { @@ -38,18 +36,17 @@ class WaterLeakBatchControlView extends StatelessWidget ); } - Widget _buildStatusControls( - BuildContext context, WaterLeakStatusModel status) { + Widget _buildStatusControls(BuildContext context, WaterLeakStatusModel status) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - SizedBox( - width: 170, - height: 140, - child: FirmwareUpdateWidget(deviceId: deviceIds.first, version: 2)), - const SizedBox( - width: 12, - ), + // SizedBox( + // width: 170, + // height: 140, + // child: FirmwareUpdateWidget(deviceId: deviceIds.first, version: 2)), + // const SizedBox( + // width: 12, + // ), SizedBox( width: 170, height: 140, diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index c837e40a..1772ef88 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -90,40 +90,40 @@ class HomeBloc extends Bloc { }, color: ColorsManager.primaryColor, ), - HomeItemModel( - title: 'Move in', - icon: Assets.moveinIcon, - active: false, - onPress: (context) {}, - color: ColorsManager.primaryColor, - ), - HomeItemModel( - title: 'Construction', - icon: Assets.constructionIcon, - active: false, - onPress: (context) {}, - color: ColorsManager.primaryColor, - ), - HomeItemModel( - title: 'Energy', - icon: Assets.energyIcon, - active: false, - onPress: (context) {}, - color: ColorsManager.slidingBlueColor.withOpacity(0.2), - ), - HomeItemModel( - title: 'Integrations', - icon: Assets.integrationsIcon, - active: false, - onPress: (context) {}, - color: ColorsManager.slidingBlueColor.withOpacity(0.2), - ), - HomeItemModel( - title: 'Asset', - icon: Assets.assetIcon, - active: false, - onPress: (context) {}, - color: ColorsManager.slidingBlueColor.withOpacity(0.2), - ), + // HomeItemModel( + // title: 'Move in', + // icon: Assets.moveinIcon, + // active: false, + // onPress: (context) {}, + // color: ColorsManager.primaryColor, + // ), + // HomeItemModel( + // title: 'Construction', + // icon: Assets.constructionIcon, + // active: false, + // onPress: (context) {}, + // color: ColorsManager.primaryColor, + // ), + // HomeItemModel( + // title: 'Energy', + // icon: Assets.energyIcon, + // active: false, + // onPress: (context) {}, + // color: ColorsManager.slidingBlueColor.withOpacity(0.2), + // ), + // HomeItemModel( + // title: 'Integrations', + // icon: Assets.integrationsIcon, + // active: false, + // onPress: (context) {}, + // color: ColorsManager.slidingBlueColor.withOpacity(0.2), + // ), + // HomeItemModel( + // title: 'Asset', + // icon: Assets.assetIcon, + // active: false, + // onPress: (context) {}, + // color: ColorsManager.slidingBlueColor.withOpacity(0.2), + // ), ]; } diff --git a/lib/pages/home/view/home_page_web.dart b/lib/pages/home/view/home_page_web.dart index cb806dfc..a198fa76 100644 --- a/lib/pages/home/view/home_page_web.dart +++ b/lib/pages/home/view/home_page_web.dart @@ -51,7 +51,7 @@ class HomeWebPage extends StatelessWidget { height: size.height * 0.6, width: size.width * 0.68, child: GridView.builder( - itemCount: 8, + itemCount: 3, //8 gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 4, crossAxisSpacing: 20.0, diff --git a/lib/utils/user_drop_down_menu.dart b/lib/utils/user_drop_down_menu.dart index 3a0c4194..cee9bbe9 100644 --- a/lib/utils/user_drop_down_menu.dart +++ b/lib/utils/user_drop_down_menu.dart @@ -75,26 +75,26 @@ class _UserDropdownMenuState extends State { ), ), items: [ - PopupMenuItem( - onTap: () {}, - child: ListTile( - leading: SvgPicture.asset(Assets.accountSetting), - title: Text( - "Account Settings", - style: context.textTheme.bodyMedium, - ), - ), - ), - PopupMenuItem( - onTap: () {}, - child: ListTile( - leading: SvgPicture.asset(Assets.settings), - title: Text( - "Settings", - style: context.textTheme.bodyMedium, - ), - ), - ), + // PopupMenuItem( + // onTap: () {}, + // child: ListTile( + // leading: SvgPicture.asset(Assets.accountSetting), + // title: Text( + // "Account Settings", + // style: context.textTheme.bodyMedium, + // ), + // ), + // ), + // PopupMenuItem( + // onTap: () {}, + // child: ListTile( + // leading: SvgPicture.asset(Assets.settings), + // title: Text( + // "Settings", + // style: context.textTheme.bodyMedium, + // ), + // ), + // ), PopupMenuItem( onTap: () { showDialog( @@ -211,8 +211,10 @@ class _UserDropdownMenuState extends State { elevation: 1, child: Text( 'Logout', - style: - Theme.of(context).textTheme.bodyMedium!.copyWith(fontSize: 12, color: Colors.white), + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(fontSize: 12, color: Colors.white), ), ), ), From bd1204c03aa41672a6434037ae118ee91e411785 Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 17 Dec 2024 16:51:32 +0300 Subject: [PATCH 015/175] add_user_dialog --- .../model/role_type_model.dart | 22 ++ .../users_page/bloc/users_bloc.dart | 64 +++- .../users_page/bloc/users_event.dart | 29 +- .../users_page/bloc/users_status.dart | 4 + .../users_page/view/add_user_dialog.dart | 9 +- .../users_page/view/basics_view.dart | 150 ++++++--- .../users_page/view/delete_user_dialog.dart | 8 +- .../users_page/view/roles_and_permission.dart | 294 +++++++++--------- lib/services/user_permission.dart | 36 +++ lib/utils/constants/api_const.dart | 35 ++- pubspec.lock | 40 +++ pubspec.yaml | 2 + 12 files changed, 481 insertions(+), 212 deletions(-) create mode 100644 lib/pages/roles_and_permission/model/role_type_model.dart create mode 100644 lib/services/user_permission.dart diff --git a/lib/pages/roles_and_permission/model/role_type_model.dart b/lib/pages/roles_and_permission/model/role_type_model.dart new file mode 100644 index 00000000..1705e25c --- /dev/null +++ b/lib/pages/roles_and_permission/model/role_type_model.dart @@ -0,0 +1,22 @@ +class RoleTypeModel { + final String uuid; + final String createdAt; + final String updatedAt; + final String type; + + RoleTypeModel({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.type, + }); + + factory RoleTypeModel.fromJson(Map json) { + return RoleTypeModel( + uuid: json['uuid'], + createdAt: json['createdAt'], + updatedAt: json['updatedAt'], + type: json['type'], + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart index ad5e49ce..5b846ecf 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -1,12 +1,15 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_node_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart'; import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; +import 'package:syncrow_web/services/user_permission.dart'; class UsersBloc extends Bloc { UsersBloc() : super(UsersInitial()) { @@ -16,6 +19,9 @@ class UsersBloc extends Bloc { on(_onLoadCommunityAndSpaces); on(searchTreeNode); on(isCompleteSpacesFun); + on(_getRolePermission); + on(_getPermissions); + on(searchRolePermission); } List users = []; @@ -112,7 +118,6 @@ class UsersBloc extends Bloc { void isCompleteSpacesFun( CheckSpacesStepStatus event, Emitter emit) { emit(UsersLoadingState()); - try { List selectedIds = getSelectedIds(updatedCommunities); isCompleteSpaces = selectedIds.isNotEmpty; @@ -224,4 +229,61 @@ class UsersBloc extends Bloc { } return selectedIds; } + + List roles = []; + List permissions = []; + + _getRolePermission(RoleEvent event, Emitter emit) async { + try { + emit(UsersLoadingState()); + roles = await UserPermissionApi().fetchRoles(); + add(PermissionEvent(roleUuid: roles.first.uuid)); + emit(RolePermissionInitial()); + } catch (e) { + emit(ErrorState('Error loading communities and spaces: $e')); + } + } + + _getPermissions(PermissionEvent event, Emitter emit) async { + try { + emit(UsersLoadingState()); + permissions = await UserPermissionApi().fetchPermission( + event.roleUuid == "" ? roles.first.uuid : event.roleUuid); + emit(RolePermissionInitial()); + } catch (e) { + emit(ErrorState('Error loading communities and spaces: $e')); + } + } + + bool _searchRolePermission(List nodes, String searchTerm) { + bool anyMatch = false; + for (var node in nodes) { + bool isMatch = + node.title.toLowerCase().contains(searchTerm.toLowerCase()); + bool childMatch = _searchRolePermission(node.subOptions, searchTerm); + node.isHighlighted = isMatch || childMatch; + + anyMatch = anyMatch || node.isHighlighted; + } + return anyMatch; + } + + void searchRolePermission(SearchPermission event, Emitter emit) { + emit(UsersLoadingState()); + if (event.searchTerm!.isEmpty) { + _clearHighlightsRolePermission(permissions); + } else { + _searchRolePermission(permissions, event.searchTerm!); + } + emit(ChangeStatusSteps()); + } + + void _clearHighlightsRolePermission(List nodes) { + for (var node in nodes) { + node.isHighlighted = false; + if (node.subOptions.isNotEmpty) { + _clearHighlightsRolePermission(node.subOptions); + } + } + } } diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart index f1675b08..6f59b495 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_node_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart'; sealed class UsersEvent extends Equatable { const UsersEvent(); @@ -10,6 +11,7 @@ class GetUsers extends UsersEvent { @override List get props => []; } + class CheckSpacesStepStatus extends UsersEvent { const CheckSpacesStepStatus(); @override @@ -22,6 +24,19 @@ class LoadCommunityAndSpacesEvent extends UsersEvent { List get props => []; } +class RoleEvent extends UsersEvent { + const RoleEvent(); + @override + List get props => []; +} + +class PermissionEvent extends UsersEvent { + final String? roleUuid; + const PermissionEvent({this.roleUuid = ""}); + @override + List get props => [roleUuid]; +} + class GetBatchStatus extends UsersEvent { final List uuids; const GetBatchStatus(this.uuids); @@ -29,7 +44,6 @@ class GetBatchStatus extends UsersEvent { List get props => [uuids]; } -//LoadCommunityAndSpacesEvent class ChangeUserStatus extends UsersEvent { final String userId; final String newStatus; @@ -54,9 +68,20 @@ class SearchAnode extends UsersEvent { @override List get props => [nodes, searchTerm]; } + +class SearchPermission extends UsersEvent { + List? nodes; + String? searchTerm; + SearchPermission({this.nodes, this.searchTerm}); + @override + List get props => [nodes, searchTerm]; +} + class SelecteId extends UsersEvent { List? nodes; - SelecteId({this.nodes,}); + SelecteId({ + this.nodes, + }); @override List get props => [nodes]; } diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart index b9937f77..5d1a68f6 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart @@ -9,6 +9,10 @@ final class UsersInitial extends UsersState { @override List get props => []; } +final class RolePermissionInitial extends UsersState { + @override + List get props => []; +} final class ChangeStatusSteps extends UsersState { @override diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart index 895f4825..dc45cfdc 100644 --- a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart @@ -23,8 +23,9 @@ class _AddNewUserDialogState extends State { @override Widget build(BuildContext context) { return BlocProvider( - create: (BuildContext context) => - UsersBloc()..add(LoadCommunityAndSpacesEvent()), + create: (BuildContext context) => UsersBloc() + ..add(const LoadCommunityAndSpacesEvent()) + ..add(const RoleEvent()), child: BlocConsumer( listener: (context, state) {}, builder: (context, state) { @@ -45,7 +46,9 @@ class _AddNewUserDialogState extends State { child: Text( "Add New User", style: TextStyle( - fontSize: 18, fontWeight: FontWeight.bold), + fontSize: 20, + fontWeight: FontWeight.w700, + color: ColorsManager.secondaryColor), ), ), ), diff --git a/lib/pages/roles_and_permission/users_page/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/view/basics_view.dart index d64d837d..abf6642c 100644 --- a/lib/pages/roles_and_permission/users_page/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/view/basics_view.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:intl_phone_field/country_picker_dialog.dart'; +import 'package:intl_phone_field/intl_phone_field.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -47,9 +49,16 @@ class BasicsView extends StatelessWidget { SizedBox( child: Row( children: [ - Text( - "*", - style: TextStyle(color: ColorsManager.red), + // SizedBox( + // width: 15, + // ), + const Text( + " * ", + style: TextStyle( + color: ColorsManager.red, + fontWeight: FontWeight.w900, + fontSize: 15, + ), ), Text( 'First Name', @@ -96,8 +105,12 @@ class BasicsView extends StatelessWidget { child: Row( children: [ const Text( - "*", - style: TextStyle(color: ColorsManager.red), + " * ", + style: TextStyle( + color: ColorsManager.red, + fontWeight: FontWeight.w900, + fontSize: 15, + ), ), Text('Last Name', style: context.textTheme.bodyMedium?.copyWith( @@ -140,9 +153,13 @@ class BasicsView extends StatelessWidget { SizedBox( child: Row( children: [ - Text( - "*", - style: TextStyle(color: ColorsManager.red), + const Text( + " * ", + style: TextStyle( + color: ColorsManager.red, + fontWeight: FontWeight.w900, + fontSize: 15, + ), ), Text( 'Email Address', @@ -187,10 +204,6 @@ class BasicsView extends StatelessWidget { SizedBox( child: Row( children: [ - Text( - "*", - style: TextStyle(color: ColorsManager.red), - ), Text( 'Mobile Number', style: context.textTheme.bodyMedium?.copyWith( @@ -200,28 +213,93 @@ class BasicsView extends StatelessWidget { ), ], )), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - style: const TextStyle(color: Colors.black), - controller: _blocRole.phoneController, - decoration: inputTextFormDeco( - hintText: "05x xxx xxxx", - ).copyWith( - hintStyle: context.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w400, - fontSize: 12, - color: ColorsManager.textGray), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a phone number'; - } - return null; - }, - keyboardType: TextInputType.phone, + // InternationalPhoneNumberInput( + // spaceBetweenSelectorAndTextField: 50, + // initialValue: PhoneNumber(isoCode: 'AE'), + // inputDecoration: inputTextFormDeco( + // hintText: "x xxx xxxx", + // ).copyWith( + // hintStyle: context.textTheme.bodyMedium?.copyWith( + // fontWeight: FontWeight.w400, + // fontSize: 12, + // color: ColorsManager.textGray), + // ), + // onInputChanged: (PhoneNumber number) { + // print(number.phoneNumber); + // }, + // onInputValidated: (bool value) { + // print(value); + // }, + // selectorConfig: const SelectorConfig( + // selectorType: PhoneInputSelectorType.BOTTOM_SHEET, + // useBottomSheetSafeArea: true, + // leadingPadding: 15, + // trailingSpace: false, + // setSelectorButtonAsPrefixIcon: true), + // ignoreBlank: true, + // autoValidateMode: AutovalidateMode.disabled, + + // selectorTextStyle: + // TextStyle(color: ColorsManager.blackColor), + // // initialValue: number, + // // textFieldController: controller, + // formatInput: true, + // keyboardType: const TextInputType.numberWithOptions( + // signed: true, decimal: true), + // // inputBorder: OutlineInputBorder( + // // borderSide: + // // BorderSide(color: Colors.black,)), + // onSaved: (PhoneNumber number) { + // print('On Saved: $number'); + // }, + // textStyle: + // const TextStyle(color: ColorsManager.blackColor), + // ), + + IntlPhoneField( + pickerDialogStyle: PickerDialogStyle(), + dropdownIconPosition: IconPosition.leading, + disableLengthCheck: true, + dropdownTextStyle: TextStyle(color: Colors.black), + textInputAction: TextInputAction.done, + decoration: inputTextFormDeco( + hintText: "05x xxx xxxx", + ).copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), ), - ), + + initialCountryCode: 'AE', + style: TextStyle(color: Colors.black), + onChanged: (phone) { + print(phone.completeNumber); + }, + ) + + // Padding( + // padding: const EdgeInsets.all(8.0), + // child: TextFormField( + // style: const TextStyle(color: Colors.black), + // controller: _blocRole.phoneController, + // decoration: inputTextFormDeco( + // hintText: "05x xxx xxxx", + // ).copyWith( + // hintStyle: context.textTheme.bodyMedium?.copyWith( + // fontWeight: FontWeight.w400, + // fontSize: 12, + // color: ColorsManager.textGray), + // ), + // validator: (value) { + // if (value == null || value.isEmpty) { + // return 'Please enter a phone number'; + // } + // return null; + // }, + // keyboardType: TextInputType.phone, + // ), + // ), ], ), ), @@ -236,10 +314,6 @@ class BasicsView extends StatelessWidget { SizedBox( child: Row( children: [ - Text( - "*", - style: TextStyle(color: ColorsManager.red), - ), Text( 'Job Title', style: context.textTheme.bodyMedium?.copyWith( diff --git a/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart index 090d4fe3..f10fd4ed 100644 --- a/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart @@ -5,14 +5,14 @@ import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_blo import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class AddNewUserDialog extends StatefulWidget { - const AddNewUserDialog({super.key}); +class DeleteUserDialog extends StatefulWidget { + const DeleteUserDialog({super.key}); @override - _AddNewUserDialogState createState() => _AddNewUserDialogState(); + _DeleteUserDialogState createState() => _DeleteUserDialogState(); } -class _AddNewUserDialogState extends State { +class _DeleteUserDialogState extends State { int currentStep = 1; @override diff --git a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart index 812a3170..a8c186e8 100644 --- a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart +++ b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -34,7 +35,12 @@ class RolesAndPermission extends StatelessWidget { const SizedBox( height: 15, ), - const SizedBox(width: 300, height: 110, child: DropdownExample()), + SizedBox( + width: 300, + height: 110, + child: DropdownExample( + bloc: _blocRole, + )), const SizedBox(height: 10), Expanded( child: SizedBox( @@ -64,6 +70,11 @@ class RolesAndPermission extends StatelessWidget { style: const TextStyle(color: Colors.black), controller: _blocRole.firstNameController, + onChanged: (value) { + _blocRole.add(SearchPermission( + nodes: _blocRole.permissions, + searchTerm: value)); + }, decoration: textBoxDecoration(radios: 20)! .copyWith( fillColor: Colors.white, @@ -97,7 +108,9 @@ class RolesAndPermission extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: Container( color: ColorsManager.whiteColors, - child: const DeviceManagement()))) + child: DeviceManagement( + bloc: _blocRole, + )))) ], ), ), @@ -111,7 +124,8 @@ class RolesAndPermission extends StatelessWidget { } class DropdownExample extends StatefulWidget { - const DropdownExample({super.key}); + final UsersBloc? bloc; + const DropdownExample({super.key, this.bloc}); @override _DropdownExampleState createState() => _DropdownExampleState(); @@ -119,7 +133,13 @@ class DropdownExample extends StatefulWidget { class _DropdownExampleState extends State { String? selectedRole; - List roles = ['Admin', 'User', 'Guest', 'Moderator']; + @override + void initState() { + super.initState(); + if (widget.bloc != null && widget.bloc!.roles.isNotEmpty) { + selectedRole = widget.bloc!.roles.first.uuid; + } + } @override Widget build(BuildContext context) { @@ -140,19 +160,20 @@ class _DropdownExampleState extends State { SizedBox( child: DropdownButtonFormField( alignment: Alignment.center, - focusColor: ColorsManager.whiteColors, + focusColor: Colors.white, autofocus: true, value: selectedRole, - items: roles.map((role) { - return DropdownMenuItem( - value: role, - child: Text(role), + items: widget.bloc!.roles.map((role) { + return DropdownMenuItem( + value: role.uuid, + child: Text(role.type), ); }).toList(), onChanged: (value) { setState(() { selectedRole = value; }); + widget.bloc!.add(PermissionEvent(roleUuid: selectedRole)); }, padding: EdgeInsets.zero, icon: const SizedBox.shrink(), @@ -168,13 +189,13 @@ class _DropdownExampleState extends State { width: 70, height: 50, decoration: BoxDecoration( - color: ColorsManager.graysColor, + color: Colors.grey[200], borderRadius: const BorderRadius.only( bottomRight: Radius.circular(10), topRight: Radius.circular(10), ), border: Border.all( - color: ColorsManager.grayBorder, + color: Colors.grey, width: 1.0, ), ), @@ -192,60 +213,17 @@ class _DropdownExampleState extends State { } class DeviceManagement extends StatefulWidget { - const DeviceManagement({Key? key}) : super(key: key); + final UsersBloc? bloc; + const DeviceManagement({Key? key, this.bloc}) : super(key: key); @override _DeviceManagementState createState() => _DeviceManagementState(); } class _DeviceManagementState extends State { - - - final List options = [ - MainRoleOption( - id: '1', - title: "Device Management", - subOptions: [ - SubRoleOption( - id: '11', - title: "Manage devices in private spaces", - children: [ - ChildRoleOption(id: '111', title: "Control"), - ChildRoleOption(id: '112', title: "Assign device"), - ChildRoleOption(id: '113', title: "View"), - ], - ), - SubRoleOption( - id: '12', - title: "Manage", - children: [ - ChildRoleOption(id: '121', title: "cc"), - ChildRoleOption(id: '122', title: "Assign"), - ChildRoleOption(id: '123', title: "s"), - ], - ), - ], - ), - MainRoleOption( - id: '2', - title: "Device Management", - subOptions: [ - SubRoleOption( - id: '22', - title: "Manage devices in private spaces", - children: [ - ChildRoleOption(id: '211', title: "Control"), - ChildRoleOption(id: '212', title: "Assign device"), - ChildRoleOption(id: '213', title: "View"), - ], - ), - ], - ), - ]; - void toggleOptionById(String id) { setState(() { - for (var mainOption in options) { + for (var mainOption in widget.bloc!.permissions) { if (mainOption.id == id) { final isChecked = checkifOneOfthemChecked(mainOption) == CheckState.all; @@ -253,7 +231,7 @@ class _DeviceManagementState extends State { for (var subOption in mainOption.subOptions) { subOption.isChecked = !isChecked; - for (var child in subOption.children) { + for (var child in subOption.subOptions) { child.isChecked = !isChecked; } } @@ -263,7 +241,7 @@ class _DeviceManagementState extends State { for (var subOption in mainOption.subOptions) { if (subOption.id == id) { subOption.isChecked = !subOption.isChecked; - for (var child in subOption.children) { + for (var child in subOption.subOptions) { child.isChecked = subOption.isChecked; } mainOption.isChecked = @@ -271,11 +249,11 @@ class _DeviceManagementState extends State { return; } - for (var child in subOption.children) { + for (var child in subOption.subOptions) { if (child.id == id) { child.isChecked = !child.isChecked; subOption.isChecked = - subOption.children.every((child) => child.isChecked); + subOption.subOptions.every((child) => child.isChecked); mainOption.isChecked = mainOption.subOptions.every((sub) => sub.isChecked); return; @@ -286,7 +264,7 @@ class _DeviceManagementState extends State { }); } - CheckState checkifOneOfthemChecked(MainRoleOption mainOption) { + CheckState checkifOneOfthemChecked(PermissionOption mainOption) { bool allSelected = true; bool someSelected = false; @@ -297,7 +275,7 @@ class _DeviceManagementState extends State { allSelected = false; } - for (var child in subOption.children) { + for (var child in subOption.subOptions) { if (child.isChecked) { someSelected = true; } else { @@ -319,16 +297,16 @@ class _DeviceManagementState extends State { Widget build(BuildContext context) { return ListView.builder( padding: const EdgeInsets.all(8), - itemCount: options.length, + itemCount: widget.bloc!.permissions.length, itemBuilder: (context, index) { - final option = options[index]; + final option = widget.bloc!.permissions[index]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ InkWell( - onTap: () => toggleOptionById(option.id), + // onTap: () => toggleOptionById(option.id), child: Builder( builder: (context) { final checkState = checkifOneOfthemChecked(option); @@ -372,50 +350,55 @@ class _DeviceManagementState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - InkWell( - onTap: () => toggleOptionById(subOption.id), - child: Builder( - builder: (context) { - final checkState = - checkifOneOfthemChecked(MainRoleOption( - id: subOption.id, - title: subOption.title, - subOptions: [subOption], - )); + Container( + color: option.isHighlighted + ? Colors.blue.shade50 + : Colors.white, + child: Row( + children: [ + InkWell( + // onTap: () => toggleOptionById(subOption.id), + child: Builder( + builder: (context) { + final checkState = + checkifOneOfthemChecked(PermissionOption( + id: subOption.id, + title: subOption.title, + subOptions: [subOption], + )); - if (checkState == CheckState.all) { - return Image.asset( - Assets.CheckBoxChecked, - width: 20, - height: 20, - ); - } else if (checkState == CheckState.some) { - return Image.asset( - Assets.rectangleCheckBox, - width: 20, - height: 20, - ); - } else { - return Image.asset( - Assets.emptyBox, - width: 20, - height: 20, - ); - } - }, + if (checkState == CheckState.all) { + return Image.asset( + Assets.CheckBoxChecked, + width: 20, + height: 20, + ); + } else if (checkState == CheckState.some) { + return Image.asset( + Assets.rectangleCheckBox, + width: 20, + height: 20, + ); + } else { + return Image.asset( + Assets.emptyBox, + width: 20, + height: 20, + ); + } + }, + ), ), - ), - const SizedBox(width: 8), - Text( - subOption.title, - style: context.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w700, - fontSize: 12, - color: ColorsManager.lightGreyColor), - ), - ], + const SizedBox(width: 8), + Text( + subOption.title, + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 12, + color: ColorsManager.lightGreyColor), + ), + ], + ), ), Padding( padding: const EdgeInsets.only(left: 50.0), @@ -424,15 +407,18 @@ class _DeviceManagementState extends State { physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, // 2 items per row - mainAxisSpacing: 2.0, // Space between rows - crossAxisSpacing: 0.2, // Space between columns - childAspectRatio: 5, // Adjust aspect ratio as needed + crossAxisCount: 2, + mainAxisSpacing: 2.0, + crossAxisSpacing: 0.2, + childAspectRatio: 5, ), - itemCount: subOption.children.length, + itemCount: subOption.subOptions.length, itemBuilder: (context, index) { - final child = subOption.children[index]; + final child = subOption.subOptions[index]; return CheckboxListTile( + selectedTileColor: child.isHighlighted + ? Colors.blue.shade50 + : Colors.white, dense: true, controlAffinity: ListTileControlAffinity.leading, title: Text( @@ -444,6 +430,7 @@ class _DeviceManagementState extends State { ), value: child.isChecked, onChanged: (value) => toggleOptionById(child.id), + enabled: false, ); }, ), @@ -458,43 +445,44 @@ class _DeviceManagementState extends State { } } -class MainRoleOption { - String id; - String title; - bool isChecked; - List subOptions; - MainRoleOption({ - required this.id, - required this.title, - this.isChecked = false, - this.subOptions = const [], - }); -} - -class SubRoleOption { - String id; - String title; - bool isChecked; - List children; - - SubRoleOption({ - required this.id, - required this.title, - this.isChecked = false, - this.children = const [], - }); -} - -class ChildRoleOption { - String id; - String title; - bool isChecked; - - ChildRoleOption({ - required this.id, - required this.title, - this.isChecked = false, - }); -} enum CheckState { none, some, all } + +class PermissionOption { + String id; + String title; + bool isChecked; + bool isHighlighted; + List subOptions; + + PermissionOption({ + required this.id, + required this.title, + this.isChecked = false, + this.isHighlighted = false, + this.subOptions = const [], + }); + + factory PermissionOption.fromJson(Map json) { + return PermissionOption( + id: json['id'] ?? '', + title: json['title'] ?? '', + isChecked: json['isChecked'] ?? false, + isHighlighted: json['isHighlighted'] ?? false, + subOptions: (json['subOptions'] as List?) + ?.map((sub) => PermissionOption.fromJson(sub)) + .toList() ?? + [], + ); + } + + Map toJson() { + return { + 'id': id, + 'title': title, + 'isChecked': isChecked, + 'isHighlighted': isHighlighted, + 'subOptions': subOptions.map((sub) => sub.toJson()).toList(), + }; + } +} diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart new file mode 100644 index 00000000..91091dbe --- /dev/null +++ b/lib/services/user_permission.dart @@ -0,0 +1,36 @@ + +import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; +import 'package:syncrow_web/utils/constants/api_const.dart'; + +class UserPermissionApi { + static final HTTPService _httpService = HTTPService(); + + fetchRoles() async { + final response = await _httpService.get( + path: ApiEndpoints.roleTypes, + showServerMessage: true, + expectedResponseModel: (json) { + final List fetchedRoles = (json['data'] as List) + .map((item) => RoleTypeModel.fromJson(item)) + .toList(); + return fetchedRoles; + }, + ); + return response; + } + + Future> fetchPermission(roleUuid) async { + final response = await _httpService.get( + path: ApiEndpoints.permission.replaceAll("roleUuid", roleUuid), + showServerMessage: true, + expectedResponseModel: (json) { + return (json as List) + .map((data) => PermissionOption.fromJson(data)) + .toList(); + }, + ); + return response ?? []; + } +} diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 007b488d..752c5bea 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -11,12 +11,14 @@ abstract class ApiEndpoints { static const String visitorPassword = '/visitor-password'; static const String getDevices = '/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'; @@ -38,8 +40,10 @@ abstract class ApiEndpoints { // Space Module static const String createSpace = '/communities/{communityId}/spaces'; static const String listSpaces = '/communities/{communityId}/spaces'; - static const String deleteSpace = '/communities/{communityId}/spaces/{spaceId}'; - static const String updateSpace = '/communities/{communityId}/spaces/{spaceId}'; + static const String deleteSpace = + '/communities/{communityId}/spaces/{spaceId}'; + static const String updateSpace = + '/communities/{communityId}/spaces/{spaceId}'; static const String getSpace = '/communities/{communityId}/spaces/{spaceId}'; static const String getSpaceHierarchy = '/communities/{communityId}/spaces'; @@ -55,11 +59,15 @@ abstract class ApiEndpoints { '/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'; @@ -68,13 +76,18 @@ abstract class ApiEndpoints { static const String getIconScene = '/scene/icon'; static const String createScene = '/scene/tap-to-run'; static const String createAutomation = '/automation'; - static const String getUnitScenes = '/communities/{communityUuid}/spaces/{spaceUuid}/scenes'; - static const String getAutomationDetails = '/automation/details/{automationId}'; + static const String getUnitScenes = + '/communities/{communityUuid}/spaces/{spaceUuid}/scenes'; + 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}'; static const String deleteAutomation = '/automation/{automationId}'; - static const String updateScene = '/scene/tap-to-run/{sceneId}'; + static const String updateScene = '/scene/tap-to-run/{sceneId}'; static const String updateAutomation = '/automation/{automationId}'; + static const String roleTypes = '/role/types'; + static const String permission = '/permission/{roleUuid}'; + // static const String updateAutomation = '/automation/{automationId}'; } diff --git a/pubspec.lock b/pubspec.lock index ea2315d7..98c62a94 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -304,6 +304,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.19.0" + intl_phone_field: + dependency: "direct main" + description: + name: intl_phone_field + sha256: "73819d3dfcb68d2c85663606f6842597c3ddf6688ac777f051b17814fe767bbf" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + intl_phone_number_input: + dependency: "direct main" + description: + name: intl_phone_number_input + sha256: "1c4328713a9503ab26a1fdbb6b00b4cada68c18aac922b35bedbc72eff1297c3" + url: "https://pub.dev" + source: hosted + version: "0.7.4" js: dependency: transitive description: @@ -336,6 +352,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + libphonenumber_platform_interface: + dependency: transitive + description: + name: libphonenumber_platform_interface + sha256: f801f6c65523f56504b83f0890e6dad584ab3a7507dca65fec0eed640afea40f + url: "https://pub.dev" + source: hosted + version: "0.4.2" + libphonenumber_plugin: + dependency: transitive + description: + name: libphonenumber_plugin + sha256: c615021d9816fbda2b2587881019ed595ecdf54d999652d7e4cce0e1f026368c + url: "https://pub.dev" + source: hosted + version: "0.3.3" + libphonenumber_web: + dependency: transitive + description: + name: libphonenumber_web + sha256: "8186f420dbe97c3132283e52819daff1e55d60d6db46f7ea5ac42f42a28cc2ef" + url: "https://pub.dev" + source: hosted + version: "0.3.2" lints: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ffff62ac..c99481d1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -52,6 +52,8 @@ dependencies: fl_chart: ^0.69.0 uuid: ^4.4.2 time_picker_spinner: ^1.0.0 + intl_phone_field: ^3.2.0 + intl_phone_number_input: ^0.7.4 dev_dependencies: flutter_test: From 298aab5116cb6eca92cdf4f846ccb834c0af678a Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 17 Dec 2024 17:07:14 +0300 Subject: [PATCH 016/175] send_invite_user --- .../users_page/bloc/users_bloc.dart | 13 +++++++++ lib/services/user_permission.dart | 29 ++++++++++++++++++- lib/utils/constants/api_const.dart | 1 + 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart index 5b846ecf..ec3139b7 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -268,6 +268,19 @@ class UsersBloc extends Bloc { return anyMatch; } + _sendInvitUser(List nodes, String searchTerm) async { + emit(UsersLoadingState()); + await UserPermissionApi().sendInviteUser( + email: emailController.text, + firstName: firstNameController.text, + jobTitle: jobTitleController.text, + lastName: lastNameController.text, + phoneNumber: phoneController.text, + roleUuid: '', + spaceUuids: selectedIds); + emit(RolePermissionInitial()); + } + void searchRolePermission(SearchPermission event, Emitter emit) { emit(UsersLoadingState()); if (event.searchTerm!.isEmpty) { diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index 91091dbe..22083191 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -1,4 +1,3 @@ - import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart'; import 'package:syncrow_web/services/api/http_service.dart'; @@ -33,4 +32,32 @@ class UserPermissionApi { ); return response ?? []; } + + Future sendInviteUser({ + String? firstName, + String? lastName, + String? email, + String? jobTitle, + String? phoneNumber, + String? roleUuid, + List? spaceUuids, + }) async { + final response = await _httpService.post( + path: ApiEndpoints.permission, + showServerMessage: true, + body: { + "firstName": firstName, + "lastName": lastName, + "email": email, + "jobTitle": jobTitle, + "phoneNumber": phoneNumber, + "roleUuid": roleUuid, + "spaceUuids": spaceUuids + }, + expectedResponseModel: (json) { + print(json); + }, + ); + return response ?? []; + } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 752c5bea..28923538 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -89,5 +89,6 @@ abstract class ApiEndpoints { static const String updateAutomation = '/automation/{automationId}'; static const String roleTypes = '/role/types'; static const String permission = '/permission/{roleUuid}'; + static const String inviteUser = '/invite-user'; // static const String updateAutomation = '/automation/{automationId}'; } From 573852b4b4c72f538b50e2bc6ef7726fdce0ef96 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 18 Dec 2024 17:11:37 +0300 Subject: [PATCH 017/175] user_invite --- assets/icons/user_management.svg | 7 ++ lib/pages/home/bloc/home_bloc.dart | 4 +- .../users_page/bloc/users_bloc.dart | 76 +++++++++++------ .../users_page/bloc/users_event.dart | 19 +++++ .../users_page/bloc/users_status.dart | 18 ++++ .../users_page/view/add_user_dialog.dart | 83 ++++++++++++------- .../users_page/view/basics_view.dart | 79 +++--------------- .../users_page/view/roles_and_permission.dart | 1 + lib/utils/constants/assets.dart | 12 +-- lib/utils/user_drop_down_menu.dart | 43 +++++++--- 10 files changed, 198 insertions(+), 144 deletions(-) create mode 100644 assets/icons/user_management.svg diff --git a/assets/icons/user_management.svg b/assets/icons/user_management.svg new file mode 100644 index 00000000..3255117a --- /dev/null +++ b/assets/icons/user_management.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index 04c35295..022354fa 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -95,9 +95,7 @@ class HomeBloc extends Bloc { title: 'Move in', icon: Assets.moveinIcon, active: true, - onPress: (context) { - context.go(RoutesConst.rolesAndPermissions); - }, + onPress: (context) {}, color: ColorsManager.primaryColor, ), HomeItemModel( diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart index ec3139b7..f0e3c9ad 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -22,9 +22,20 @@ class UsersBloc extends Bloc { on(_getRolePermission); on(_getPermissions); on(searchRolePermission); + on(_sendInvitUser); + on(_validateBasicsStep); + on(isCompleteRoleFun); + } + void _validateBasicsStep(ValidateBasicsStep event, Emitter emit) { + if (formKey.currentState?.validate() ?? false) { + emit(const BasicsStepValidState()); + } else { + emit(const BasicsStepInvalidState()); + } } List users = []; + String roleSelected = ''; Future _getUsers(GetUsers event, Emitter emit) async { emit(UsersLoadingState()); @@ -106,31 +117,42 @@ class UsersBloc extends Bloc { int numberSpaces = 0; int numberRole = 0; - isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { + // isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { + // emit(UsersLoadingState()); + // isCompleteBasics = firstNameController.text.isNotEmpty && + // lastNameController.text.isNotEmpty && + // emailController.text.isNotEmpty; + // emit(ChangeStatusSteps()); + // return isCompleteBasics; + // } + + bool isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { emit(UsersLoadingState()); + + final emailRegex = RegExp( + r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', + ); isCompleteBasics = firstNameController.text.isNotEmpty && lastNameController.text.isNotEmpty && - emailController.text.isNotEmpty; + emailController.text.isNotEmpty && + emailRegex.hasMatch(emailController.text); + emit(ChangeStatusSteps()); - return isCompleteBasics; + return isCompleteBasics!; } void isCompleteSpacesFun( CheckSpacesStepStatus event, Emitter emit) { emit(UsersLoadingState()); - try { - List selectedIds = getSelectedIds(updatedCommunities); - isCompleteSpaces = selectedIds.isNotEmpty; - } catch (e) { - emit(ErrorState('Error while retrieving selected IDs: $e')); - return; - } - + List selectedIds = getSelectedIds(updatedCommunities); + isCompleteSpaces = selectedIds.isNotEmpty; emit(ChangeStatusSteps()); } - bool checkRolePermissions() { - return true; + void isCompleteRoleFun(CheckRoleStepStatus event, Emitter emit) { + emit(UsersLoadingState()); + isCompleteRolePermissions = roleSelected != ''; + emit(ChangeStatusSteps()); } Future> _fetchSpacesForCommunity( @@ -249,6 +271,7 @@ class UsersBloc extends Bloc { emit(UsersLoadingState()); permissions = await UserPermissionApi().fetchPermission( event.roleUuid == "" ? roles.first.uuid : event.roleUuid); + roleSelected = event.roleUuid!; emit(RolePermissionInitial()); } catch (e) { emit(ErrorState('Error loading communities and spaces: $e')); @@ -268,17 +291,22 @@ class UsersBloc extends Bloc { return anyMatch; } - _sendInvitUser(List nodes, String searchTerm) async { - emit(UsersLoadingState()); - await UserPermissionApi().sendInviteUser( - email: emailController.text, - firstName: firstNameController.text, - jobTitle: jobTitleController.text, - lastName: lastNameController.text, - phoneNumber: phoneController.text, - roleUuid: '', - spaceUuids: selectedIds); - emit(RolePermissionInitial()); + _sendInvitUser(SendInviteUsers event, Emitter emit) async { + try { + emit(UsersLoadingState()); + List selectedIds = getSelectedIds(updatedCommunities); + await UserPermissionApi().sendInviteUser( + email: emailController.text, + firstName: firstNameController.text, + jobTitle: jobTitleController.text, + lastName: lastNameController.text, + phoneNumber: phoneController.text, + roleUuid: roleSelected, + spaceUuids: selectedIds); + emit(SaveState()); + } catch (e) { + emit(ErrorState('Error: $e')); + } } void searchRolePermission(SearchPermission event, Emitter emit) { diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart index 6f59b495..633c1ea5 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart @@ -12,12 +12,25 @@ class GetUsers extends UsersEvent { List get props => []; } +class SendInviteUsers extends UsersEvent { + const SendInviteUsers(); + @override + List get props => []; +} + class CheckSpacesStepStatus extends UsersEvent { const CheckSpacesStepStatus(); @override List get props => []; } +class CheckRoleStepStatus extends UsersEvent { + const CheckRoleStepStatus(); + @override + List get props => []; +} + + class LoadCommunityAndSpacesEvent extends UsersEvent { const LoadCommunityAndSpacesEvent(); @override @@ -85,3 +98,9 @@ class SelecteId extends UsersEvent { @override List get props => [nodes]; } + +class ValidateBasicsStep extends UsersEvent { + const ValidateBasicsStep(); + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart index 5d1a68f6..b5fc01d7 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart @@ -9,6 +9,7 @@ final class UsersInitial extends UsersState { @override List get props => []; } + final class RolePermissionInitial extends UsersState { @override List get props => []; @@ -24,6 +25,11 @@ final class UsersLoadingState extends UsersState { List get props => []; } +final class SaveState extends UsersState { + @override + List get props => []; +} + final class UsersLoadedState extends UsersState { List users = []; UsersLoadedState({required this.users}); @@ -59,3 +65,15 @@ final class ChangeTapStatus extends UsersState { @override List get props => [select]; } + +final class BasicsStepValidState extends UsersState { + const BasicsStepValidState(); + @override + List get props => []; +} + +class BasicsStepInvalidState extends UsersState { + const BasicsStepInvalidState(); + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart index dc45cfdc..cae89058 100644 --- a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart @@ -88,37 +88,6 @@ class _AddNewUserDialogState extends State { child: _getFormContent(), ), const SizedBox(height: 20), - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text("Cancel"), - ), - ElevatedButton( - onPressed: () { - if (_blocRole.formKey.currentState - ?.validate() ?? - false) { - // Proceed to next step or finish - setState(() { - if (currentStep < 3) { - currentStep++; - } else { - Navigator.of(context).pop(); - } - }); - } - }, - child: Text(currentStep < 3 - ? "Next" - : "Finish"), - ), - ], - ), ], ), ), @@ -126,6 +95,53 @@ class _AddNewUserDialogState extends State { ], ), ), + const Divider(), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: const Text("Cancel"), + ), + InkWell( + onTap: () { + setState(() { + if (currentStep < 3) { + currentStep++; + if (currentStep == 2) { + _blocRole.add(const CheckStepStatus()); + } else if (currentStep == 3) { + _blocRole + .add(const CheckSpacesStepStatus()); + _blocRole + .add(const CheckSpacesStepStatus()); + } else { + _blocRole.add(const SendInviteUsers()); + } + } + }); + }, + child: Text( + currentStep < 3 ? "Next" : "Save", + style: TextStyle( + color: (_blocRole.isCompleteSpaces == false || + _blocRole.isCompleteBasics == + false || + _blocRole + .isCompleteRolePermissions == + false) && + currentStep == 3 + ? ColorsManager.grayColor + : ColorsManager.secondaryColor), + ), + ), + ], + ), + ), ], ), )); @@ -209,8 +225,12 @@ class _AddNewUserDialogState extends State { return GestureDetector( onTap: () { setState(() { + bloc.add(const CheckSpacesStepStatus()); currentStep = step; }); + Future.delayed(const Duration(milliseconds: 500), () { + bloc.add(const ValidateBasicsStep()); + }); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -270,6 +290,7 @@ class _AddNewUserDialogState extends State { setState(() { currentStep = step; bloc.add(const CheckSpacesStepStatus()); + bloc.add(const CheckStepStatus()); }); }, child: Column( diff --git a/lib/pages/roles_and_permission/users_page/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/view/basics_view.dart index abf6642c..22398ed3 100644 --- a/lib/pages/roles_and_permission/users_page/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/view/basics_view.dart @@ -14,6 +14,9 @@ class BasicsView extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { final _blocRole = BlocProvider.of(context); + if (state is BasicsStepInvalidState) { + _blocRole.formKey.currentState?.validate(); + } return Form( key: _blocRole.formKey, child: ListView( @@ -184,7 +187,14 @@ class BasicsView extends StatelessWidget { ), validator: (value) { if (value == null || value.isEmpty) { - return 'Enter last name'; + return 'Enter Email Address'; + } + // Regular expression for email validation + final emailRegex = RegExp( + r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', + ); + if (!emailRegex.hasMatch(value)) { + return 'Enter a valid Email Address'; } return null; }, @@ -213,49 +223,6 @@ class BasicsView extends StatelessWidget { ), ], )), - // InternationalPhoneNumberInput( - // spaceBetweenSelectorAndTextField: 50, - // initialValue: PhoneNumber(isoCode: 'AE'), - // inputDecoration: inputTextFormDeco( - // hintText: "x xxx xxxx", - // ).copyWith( - // hintStyle: context.textTheme.bodyMedium?.copyWith( - // fontWeight: FontWeight.w400, - // fontSize: 12, - // color: ColorsManager.textGray), - // ), - // onInputChanged: (PhoneNumber number) { - // print(number.phoneNumber); - // }, - // onInputValidated: (bool value) { - // print(value); - // }, - // selectorConfig: const SelectorConfig( - // selectorType: PhoneInputSelectorType.BOTTOM_SHEET, - // useBottomSheetSafeArea: true, - // leadingPadding: 15, - // trailingSpace: false, - // setSelectorButtonAsPrefixIcon: true), - // ignoreBlank: true, - // autoValidateMode: AutovalidateMode.disabled, - - // selectorTextStyle: - // TextStyle(color: ColorsManager.blackColor), - // // initialValue: number, - // // textFieldController: controller, - // formatInput: true, - // keyboardType: const TextInputType.numberWithOptions( - // signed: true, decimal: true), - // // inputBorder: OutlineInputBorder( - // // borderSide: - // // BorderSide(color: Colors.black,)), - // onSaved: (PhoneNumber number) { - // print('On Saved: $number'); - // }, - // textStyle: - // const TextStyle(color: ColorsManager.blackColor), - // ), - IntlPhoneField( pickerDialogStyle: PickerDialogStyle(), dropdownIconPosition: IconPosition.leading, @@ -270,36 +237,12 @@ class BasicsView extends StatelessWidget { fontSize: 12, color: ColorsManager.textGray), ), - initialCountryCode: 'AE', style: TextStyle(color: Colors.black), onChanged: (phone) { print(phone.completeNumber); }, ) - - // Padding( - // padding: const EdgeInsets.all(8.0), - // child: TextFormField( - // style: const TextStyle(color: Colors.black), - // controller: _blocRole.phoneController, - // decoration: inputTextFormDeco( - // hintText: "05x xxx xxxx", - // ).copyWith( - // hintStyle: context.textTheme.bodyMedium?.copyWith( - // fontWeight: FontWeight.w400, - // fontSize: 12, - // color: ColorsManager.textGray), - // ), - // validator: (value) { - // if (value == null || value.isEmpty) { - // return 'Please enter a phone number'; - // } - // return null; - // }, - // keyboardType: TextInputType.phone, - // ), - // ), ], ), ), diff --git a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart index a8c186e8..9b7ab542 100644 --- a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart +++ b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart @@ -71,6 +71,7 @@ class RolesAndPermission extends StatelessWidget { const TextStyle(color: Colors.black), controller: _blocRole.firstNameController, onChanged: (value) { + _blocRole.add(SearchPermission( nodes: _blocRole.permissions, searchTerm: value)); diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index acb314d9..8b271d5e 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -389,10 +389,10 @@ class Assets { 'assets/icons/current_process_icon.svg'; static const String uncomplete_ProcessIcon = 'assets/icons/uncompleate_process_icon.svg'; - static const String wrongProcessIcon = - 'assets/icons/wrong_process_icon.svg'; - static const String arrowForward = - 'assets/icons/arrow_forward.svg'; - static const String arrowDown = - 'assets/icons/arrow_down.svg'; + static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg'; + static const String arrowForward = 'assets/icons/arrow_forward.svg'; + static const String arrowDown = 'assets/icons/arrow_down.svg'; + + static const String userManagement = 'assets/icons/user_management.svg'; } +//user_management.svg diff --git a/lib/utils/user_drop_down_menu.dart b/lib/utils/user_drop_down_menu.dart index 3a0c4194..5bdc18fd 100644 --- a/lib/utils/user_drop_down_menu.dart +++ b/lib/utils/user_drop_down_menu.dart @@ -53,7 +53,8 @@ class _UserDropdownMenuState extends State { } Future _showPopupMenu(BuildContext context) async { - final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox; + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; final RelativeRect position = RelativeRect.fromRect( Rect.fromLTRB( overlay.size.width, @@ -86,11 +87,13 @@ class _UserDropdownMenuState extends State { ), ), PopupMenuItem( - onTap: () {}, + onTap: () { + context.go(RoutesConst.rolesAndPermissions); + }, child: ListTile( - leading: SvgPicture.asset(Assets.settings), + leading: SvgPicture.asset(Assets.userManagement), title: Text( - "Settings", + "User Management", style: context.textTheme.bodyMedium, ), ), @@ -107,7 +110,8 @@ class _UserDropdownMenuState extends State { height: 200, width: 400, child: Padding( - padding: const EdgeInsets.only(top: 24, left: 24, right: 24), + padding: + const EdgeInsets.only(top: 24, left: 24, right: 24), child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ @@ -120,7 +124,10 @@ class _UserDropdownMenuState extends State { padding: const EdgeInsets.only(top: 16), child: Text( 'Log out of your Syncrow account', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black, @@ -151,11 +158,15 @@ class _UserDropdownMenuState extends State { ), Expanded( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, children: [ Text( '${widget.user?.firstName ?? ''} ${widget.user?.lastName}', - style: Theme.of(context).textTheme.titleMedium!.copyWith( + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith( color: Colors.black, fontWeight: FontWeight.bold, fontSize: 20, @@ -163,7 +174,10 @@ class _UserDropdownMenuState extends State { ), Text( ' ${widget.user?.email}', - style: Theme.of(context).textTheme.bodySmall!.copyWith( + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( color: Colors.black, ), ), @@ -189,7 +203,10 @@ class _UserDropdownMenuState extends State { elevation: 1, child: Text( 'Cancel', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( fontSize: 12, color: Colors.black, ), @@ -211,8 +228,10 @@ class _UserDropdownMenuState extends State { elevation: 1, child: Text( 'Logout', - style: - Theme.of(context).textTheme.bodyMedium!.copyWith(fontSize: 12, color: Colors.white), + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(fontSize: 12, color: Colors.white), ), ), ), From c160220fcabfda0a1f05c741f2e318434723f2b3 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Mon, 23 Dec 2024 00:52:05 +0300 Subject: [PATCH 018/175] Hide region field --- lib/pages/auth/bloc/auth_bloc.dart | 14 ++-- lib/pages/auth/bloc/auth_event.dart | 14 ++-- .../auth/model/login_with_email_model.dart | 8 +- .../auth/view/forget_password_web_page.dart | 30 +++---- lib/pages/auth/view/login_mobile_page.dart | 84 ++++++++++--------- lib/pages/auth/view/login_web_page.dart | 16 ++-- lib/services/auth_api.dart | 6 +- 7 files changed, 86 insertions(+), 86 deletions(-) diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index 95ecb0ad..b931d90d 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -52,7 +52,8 @@ class AuthBloc extends Bloc { try { forgetEmailValidate = ''; _remainingTime = (await AuthenticationAPI.sendOtp( - email: forgetEmailController.text, regionUuid: regionUuid))!; + email: forgetEmailController.text, + ))!; } on DioException catch (e) { if (e.response!.statusCode == 400) { final errorData = e.response!.data; @@ -139,7 +140,7 @@ class AuthBloc extends Bloc { String validate = ''; String forgetValidate = ''; String forgetEmailValidate = ''; - String regionUuid = ''; + // String regionUuid = ''; static Token token = Token.emptyConstructor(); static UserModel? user; bool showValidationMessage = false; @@ -156,7 +157,9 @@ class AuthBloc extends Bloc { token = await AuthenticationAPI.loginWithEmail( model: LoginWithEmailModel( - email: event.username, password: event.password, regionUuid: event.regionUuid), + email: event.username, + password: event.password, + ), ); } catch (failure) { validate = 'Invalid Credentials!'; @@ -364,7 +367,7 @@ class AuthBloc extends Bloc { Future selectRegion(SelectRegionEvent event, Emitter emit) async { try { emit(AuthLoading()); - regionUuid = event.val; + // regionUuid = event.val; add(CheckEnableEvent()); emit(AuthInitialState()); } catch (e) { @@ -397,8 +400,7 @@ class AuthBloc extends Bloc { emit(AuthLoading()); checkValidate = isChecked == true && loginPasswordController.text.isNotEmpty && - loginEmailController.text.isNotEmpty && - regionUuid != ''; + loginEmailController.text.isNotEmpty; emit(LoginInitial()); return checkValidate; } diff --git a/lib/pages/auth/bloc/auth_event.dart b/lib/pages/auth/bloc/auth_event.dart index fa7e86b3..2b6a4eef 100644 --- a/lib/pages/auth/bloc/auth_event.dart +++ b/lib/pages/auth/bloc/auth_event.dart @@ -10,16 +10,16 @@ abstract class AuthEvent extends Equatable { class LoginButtonPressed extends AuthEvent { final String username; final String password; - final String regionUuid; + // final String regionUuid; const LoginButtonPressed({ required this.username, required this.password, - required this.regionUuid, + // required this.regionUuid, }); @override - List get props => [username, password, regionUuid]; + List get props => [username, password]; } class CheckBoxEvent extends AuthEvent { @@ -49,13 +49,9 @@ class UpdateTimerEvent extends AuthEvent { const UpdateTimerEvent({required this.remainingTime, required this.isButtonEnabled}); } -class ChangePasswordEvent extends AuthEvent { +class ChangePasswordEvent extends AuthEvent {} -} - -class SendOtpEvent extends AuthEvent { - -} +class SendOtpEvent extends AuthEvent {} class PasswordVisibleEvent extends AuthEvent { final bool? newValue; diff --git a/lib/pages/auth/model/login_with_email_model.dart b/lib/pages/auth/model/login_with_email_model.dart index 88be9808..ec3d4d98 100644 --- a/lib/pages/auth/model/login_with_email_model.dart +++ b/lib/pages/auth/model/login_with_email_model.dart @@ -1,19 +1,19 @@ class LoginWithEmailModel { final String email; final String password; - final String regionUuid; + // final String regionUuid; LoginWithEmailModel({ required this.email, required this.password, - required this.regionUuid, + // required this.regionUuid, }); factory LoginWithEmailModel.fromJson(Map json) { return LoginWithEmailModel( email: json['email'], password: json['password'], - regionUuid: json['regionUuid'], + // regionUuid: json['regionUuid'], ); } @@ -21,7 +21,7 @@ class LoginWithEmailModel { return { 'email': email, 'password': password, - 'regionUuid': regionUuid, + // 'regionUuid': regionUuid, }; } } diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart index c04e7ee0..f389f44f 100644 --- a/lib/pages/auth/view/forget_password_web_page.dart +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -124,18 +124,18 @@ class ForgetPasswordWebPage extends StatelessWidget { .copyWith(fontSize: 14, fontWeight: FontWeight.w400), ), const SizedBox(height: 10), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const SizedBox(height: 10), - Form( - key: forgetBloc.forgetRegionKey, - child: SizedBox( - child: - _buildDropdownField(context, forgetBloc, size))) - ], - ), + // Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // mainAxisAlignment: MainAxisAlignment.start, + // children: [ + // const SizedBox(height: 10), + // Form( + // key: forgetBloc.forgetRegionKey, + // child: SizedBox( + // child: + // _buildDropdownField(context, forgetBloc, size))) + // ], + // ), const SizedBox(height: 20), Form( key: forgetBloc.forgetEmailKey, @@ -459,9 +459,9 @@ class ForgetPasswordWebPage extends StatelessWidget { ), ); }).toList(), - value: loginBloc.regionList!.any((region) => region.id == loginBloc.regionUuid) - ? loginBloc.regionUuid - : null, + // value: loginBloc.regionList!.any((region) => region.id == loginBloc.regionUuid) + // ? loginBloc.regionUuid + // : null, onChanged: (String? value) { if (value != null) { loginBloc.add(SelectRegionEvent(val: value)); diff --git a/lib/pages/auth/view/login_mobile_page.dart b/lib/pages/auth/view/login_mobile_page.dart index 1a5c8358..4f001bc6 100644 --- a/lib/pages/auth/view/login_mobile_page.dart +++ b/lib/pages/auth/view/login_mobile_page.dart @@ -6,7 +6,7 @@ import 'package:go_router/go_router.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_event.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_state.dart'; -import 'package:syncrow_web/pages/auth/model/region_model.dart'; +// import 'package:syncrow_web/pages/auth/model/region_model.dart'; import 'package:syncrow_web/pages/auth/view/forget_password_page.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -112,44 +112,44 @@ class LoginMobilePage extends StatelessWidget { color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold), ), const SizedBox(height: 30), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Country/Region", - style: Theme.of(context).textTheme.bodySmall, - ), - SizedBox( - child: DropdownButtonFormField( - validator: loginBloc.validateRegion, - icon: const Icon( - Icons.keyboard_arrow_down_outlined, - ), - decoration: textBoxDecoration()!.copyWith( - hintText: null, - ), - hint: const Align( - alignment: Alignment.centerLeft, - child: Text( - 'Select your region/country', - textAlign: TextAlign.center, - ), - ), - isDense: true, - style: const TextStyle(color: Colors.black), - items: loginBloc.regionList!.map((RegionModel region) { - return DropdownMenuItem( - value: region.name, - child: Text(region.name), - ); - }).toList(), - onChanged: (String? value) {}, - ), - ) - ], - ), - const SizedBox(height: 20.0), + // Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // mainAxisAlignment: MainAxisAlignment.start, + // children: [ + // Text( + // "Country/Region", + // style: Theme.of(context).textTheme.bodySmall, + // ), + // SizedBox( + // child: DropdownButtonFormField( + // validator: loginBloc.validateRegion, + // icon: const Icon( + // Icons.keyboard_arrow_down_outlined, + // ), + // decoration: textBoxDecoration()!.copyWith( + // hintText: null, + // ), + // hint: const Align( + // alignment: Alignment.centerLeft, + // child: Text( + // 'Select your region/country', + // textAlign: TextAlign.center, + // ), + // ), + // isDense: true, + // style: const TextStyle(color: Colors.black), + // items: loginBloc.regionList!.map((RegionModel region) { + // return DropdownMenuItem( + // value: region.name, + // child: Text(region.name), + // ); + // }).toList(), + // onChanged: (String? value) {}, + // ), + // ) + // ], + // ), + // const SizedBox(height: 20.0), Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, @@ -207,7 +207,10 @@ class LoginMobilePage extends StatelessWidget { }, child: Text( "Forgot Password?", - style: Theme.of(context).textTheme.bodySmall, + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: ColorsManager.blackColor), ), ), ], @@ -276,7 +279,6 @@ class LoginMobilePage extends StatelessWidget { if (loginBloc.loginFormKey.currentState!.validate()) { loginBloc.add( LoginButtonPressed( - regionUuid: '', username: loginBloc.loginEmailController.text, password: loginBloc.loginPasswordController.text, ), diff --git a/lib/pages/auth/view/login_web_page.dart b/lib/pages/auth/view/login_web_page.dart index 303dac76..72cad7cc 100644 --- a/lib/pages/auth/view/login_web_page.dart +++ b/lib/pages/auth/view/login_web_page.dart @@ -155,8 +155,8 @@ class _LoginWebPageState extends State with HelperResponsiveLayout children: [ const SizedBox(height: 40), Text('Login', style: Theme.of(context).textTheme.headlineLarge), - SizedBox(height: size.height * 0.03), - _buildDropdownField(context, loginBloc, size), + // SizedBox(height: size.height * 0.03), + // _buildDropdownField(context, loginBloc, size), const SizedBox(height: 20.0), _buildEmailField(context, loginBloc), const SizedBox(height: 20.0), @@ -219,11 +219,11 @@ class _LoginWebPageState extends State with HelperResponsiveLayout ), ); }).toList(), - value: loginBloc.regionList!.any( - (region) => region.id == loginBloc.regionUuid, - ) - ? loginBloc.regionUuid - : null, + // value: loginBloc.regionList!.any( + // (region) => region.id == loginBloc.regionUuid, + // ) + // ? loginBloc.regionUuid + // : null, onChanged: (String? value) { if (value != null) { loginBloc.add(CheckEnableEvent()); @@ -462,7 +462,7 @@ class _LoginWebPageState extends State with HelperResponsiveLayout onPressed: () { if (loginBloc.loginFormKey.currentState!.validate()) { loginBloc.add(LoginButtonPressed( - regionUuid: loginBloc.regionUuid, + // regionUuid: loginBloc.regionUuid, username: loginBloc.loginEmailController.text, password: loginBloc.loginPasswordController.text, )); diff --git a/lib/services/auth_api.dart b/lib/services/auth_api.dart index 2b158cdb..190eb624 100644 --- a/lib/services/auth_api.dart +++ b/lib/services/auth_api.dart @@ -22,16 +22,16 @@ class AuthenticationAPI { }) async { final response = await HTTPService().post( path: ApiEndpoints.forgetPassword, - body: {"email": email, "password": password,"otpCode": otpCode}, + body: {"email": email, "password": password, "otpCode": otpCode}, showServerMessage: true, expectedResponseModel: (json) {}); return response; } - static Future sendOtp({required String email, required String regionUuid}) async { + static Future sendOtp({required String email}) async { final response = await HTTPService().post( path: ApiEndpoints.sendOtp, - body: {"email": email, "type": "PASSWORD", "regionUuid": regionUuid}, + body: {"email": email, "type": "PASSWORD"}, showServerMessage: true, expectedResponseModel: (json) { return json['data']['cooldown']; From 0341844ea9ac28ef341ca85e1b0589b06dfea292 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Thu, 26 Dec 2024 12:25:37 +0300 Subject: [PATCH 019/175] SP-859 --- .../widgets/routine_dialogs/ac_dialog.dart | 38 +++++++------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/lib/pages/routiens/widgets/routine_dialogs/ac_dialog.dart b/lib/pages/routiens/widgets/routine_dialogs/ac_dialog.dart index 39e342ff..7a630448 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/ac_dialog.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/ac_dialog.dart @@ -27,16 +27,15 @@ class ACHelper { context: context, builder: (BuildContext context) { return BlocProvider( - create: (_) => FunctionBloc() - ..add(InitializeFunctions(deviceSelectedFunctions ?? [])), + create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])), child: AlertDialog( contentPadding: EdgeInsets.zero, content: BlocBuilder( builder: (context, state) { final selectedFunction = state.selectedFunction; final selectedOperationName = state.selectedOperationName; - final selectedFunctionData = state.addedFunctions - .firstWhere((f) => f.functionCode == selectedFunction, + final selectedFunctionData = + state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction, orElse: () => DeviceFunctionData( entityId: '', functionCode: selectedFunction ?? '', @@ -66,10 +65,8 @@ class ACHelper { child: _buildFunctionsList( context: context, acFunctions: acFunctions, - onFunctionSelected: - (functionCode, operationName) => context - .read() - .add(SelectFunction( + onFunctionSelected: (functionCode, operationName) => + context.read().add(SelectFunction( functionCode: functionCode, operationName: operationName, )), @@ -184,7 +181,7 @@ class ACHelper { bool? removeComparators, }) { if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') { - final initialValue = selectedFunctionData?.value ?? 200; + final initialValue = selectedFunctionData?.value ?? 250; return _buildTemperatureSelector( context: context, initialValue: initialValue, @@ -197,8 +194,7 @@ class ACHelper { ); } - final selectedFn = - acFunctions.firstWhere((f) => f.code == selectedFunction); + final selectedFn = acFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); return _buildOperationalValuesList( @@ -294,8 +290,7 @@ class ACHelper { minHeight: 40.0, minWidth: 40.0, ), - isSelected: - conditions.map((c) => c == (currentCondition ?? "==")).toList(), + isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), children: conditions.map((c) => Text(c)).toList(), ); } @@ -333,10 +328,10 @@ class ACHelper { String selectCode, ) { return Slider( - value: initialValue is int ? initialValue.toDouble() : 160.0, - min: 160, + value: initialValue is int ? initialValue.toDouble() : 200.0, + min: 200, max: 300, - divisions: 14, + divisions: 10, label: '${((initialValue ?? 160) / 10).toInt()}°C', onChanged: (value) { context.read().add( @@ -389,13 +384,9 @@ class ACHelper { style: context.textTheme.bodyMedium, ), trailing: Icon( - isSelected - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, + isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, size: 24, - color: isSelected - ? ColorsManager.primaryColorWithOpacity - : ColorsManager.textGray, + color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray, ), onTap: () { if (!isSelected) { @@ -407,8 +398,7 @@ class ACHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: - selectedFunctionData?.valueDescription, + valueDescription: selectedFunctionData?.valueDescription, ), ), ); From ed2187b7ff4db67579fa7a68c838d0c8c9e4278e Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 26 Dec 2024 16:32:18 +0300 Subject: [PATCH 020/175] add_user_dialog --- assets/icons/atoz_icon.png | Bin 0 -> 1540 bytes assets/icons/filter_table_icon.svg | 3 + assets/icons/ztoa_icon.png | Bin 0 -> 1513 bytes lib/pages/common/custom_dialog.dart | 2 +- .../model/role_type_model.dart | 2 +- .../bloc/users_bloc.dart | 185 +++---- .../bloc/users_event.dart | 33 +- .../bloc/users_status.dart | 11 +- .../model/permission_option_model.dart | 40 ++ .../model/tree_node_model.dart | 0 .../view/add_user_dialog.dart | 167 +++--- .../view/basics_view.dart | 99 ++-- .../view/delete_user_dialog.dart | 4 +- .../view/permission_management.dart | 239 +++++++++ .../add_user_dialog/view/role_dropdown.dart | 115 ++++ .../view/roles_and_permission.dart | 127 +++++ .../view/spaces_access_view.dart | 8 +- .../users_table/bloc/user_table_bloc.dart | 185 +++++++ .../users_table/bloc/user_table_event.dart | 63 +++ .../users_table/bloc/user_table_state.dart | 82 +++ .../view/creation_date_filter.dart | 73 +++ .../users_table/view/de_activate_filter.dart | 73 +++ .../users_table/view/name_filter.dart | 69 +++ .../users_table/view/user_table.dart | 299 +++++++++++ .../{ => users_table}/view/users_page.dart | 83 ++- .../users_page/view/roles_and_permission.dart | 489 ------------------ .../users_page/view/user_table.dart | 256 --------- .../view/roles_and_permission_page.dart | 14 +- lib/services/space_mana_api.dart | 1 - lib/services/user_permission.dart | 68 ++- lib/utils/constants/api_const.dart | 38 +- lib/utils/constants/assets.dart | 3 + 32 files changed, 1783 insertions(+), 1048 deletions(-) create mode 100644 assets/icons/atoz_icon.png create mode 100644 assets/icons/filter_table_icon.svg create mode 100644 assets/icons/ztoa_icon.png rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/bloc/users_bloc.dart (73%) rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/bloc/users_event.dart (82%) rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/bloc/users_status.dart (88%) create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/model/tree_node_model.dart (100%) rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/view/add_user_dialog.dart (92%) rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/view/basics_view.dart (76%) rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/view/delete_user_dialog.dart (96%) create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart rename lib/pages/roles_and_permission/users_page/{ => add_user_dialog}/view/spaces_access_view.dart (98%) create mode 100644 lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart create mode 100644 lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart create mode 100644 lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart create mode 100644 lib/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart create mode 100644 lib/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart create mode 100644 lib/pages/roles_and_permission/users_page/users_table/view/name_filter.dart create mode 100644 lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart rename lib/pages/roles_and_permission/users_page/{ => users_table}/view/users_page.dart (73%) delete mode 100644 lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart delete mode 100644 lib/pages/roles_and_permission/users_page/view/user_table.dart diff --git a/assets/icons/atoz_icon.png b/assets/icons/atoz_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..33a9c351375b827aba0c385af50da41414fec209 GIT binary patch literal 1540 zcmV+f2K)JmP)5XvcS(j2Vzz()WZZ2Y}5eP`EeFL=L94RP~Gqn(}E9Y22O{hD_H{)ZsN zOs0=87KRgIdk-LAG@g6$G7}pf?oPl75Dq^K$eHx$?%rRiCu_4Y{hWwx0b3AG3Q}nX zU@{qb^k`%7B*cY6ae@*OVZh)<|9E0$buM0=Ln#O0^bkbikCAu~8U)krK4CDSpA+Ep z(J=Aw(EU-+?>>W2s4o)GZqI@=M?=H~B7Av#tSFQt)p@fQi)GcotXf%yrV~dg2Q}3| z^6Pj>@jfH)nI-9D8enr5JwwvE@nR1q}DkSS?O!4WuNr_btO zNt^(gsB}_DE`g3Ism%!#lpP?Ybeb8F;oHw_2)SGU2Z!c<`uU%SR<9?Biyd)?-wfRk z$v_@!_p)4qSG&*k@1(p`ylNpNu@||n`Xd|zPW@I|r!4bBTaOOCxFn%K3`uuL>)9d4P zX|tiE74@)mu{CUN6(OIuHvrL1ptI8hPQjBVDwn6qxHKk9SL5r`iRxNLFYFyS*VA;G zLN@Cx32F0-AbFi_En87l*WXlAW{#FCTyBAB?R3EqWj|AD0>Y$%t z{~&9=#q}}+Jdi<(%+dZ|decLBPv7&Irwlbsz^KI{znizp9 zup!N$zpGR?{y|4=?5$khu^~1KZ=QOTD1qfI)wlMd8ua>AiLSo#C`QWhVnc*<)?7U< zOC|W|Vh)-K8`1zfIdwC-q*^++lNYMqV>3_SAaA}#o7kA@e0}q$J5)WPB(yO+)Q(`5 z7sBq$^3%CR+kD#RR;B3H8Jkb!Rwz_RQD7FUcr4EWN5QK~G+&tJef{1so^SI3qNJ|Y zq}OSebyyfS}$90fhOymn97 z1zSUWxv%bTr$nLaoa&tU7@t%6NeMDp^Am7~SB73+V+l7WjuPkH5smQ5+FVqdw>88+ zl5VBX!+4eSfx8yQQ=)AcZb-;n?Mwjh#oAN5sasJ<>useHs?^D4+Eec#R4=F`7z zc$L0BpI+Yf)C_Uc;~<}QMjGW1!&P~8G^o);1S$=Il3xFDy`>BhJw^gAgW5fhkg!XB zNAjw=P?s`in@{(5sqX56r>3_bUMf}9<5I4O#T|2FYc%)JL(ZhIf?EB&GP6=S+}h^T zJ^uP1?LJ={BmNR(UCqy@zrA^{uTY|u>OV<%sVl0@8CsC!g{H%eB?R7_qD);2TAvAQ z3`+^M1S2e=^$DYWcF*J2+rYmD_;@3;{F~?i0000 + + diff --git a/assets/icons/ztoa_icon.png b/assets/icons/ztoa_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..003e572526ff10c21a3b27befd50e025ed515491 GIT binary patch literal 1513 zcmVv%6j|<|8J68Y(pyRXMbkT9R;R4yBINN)Rck+x`L2V)$kF|HEZ+2OG4ZGf1aHyM~wBpUo?(*B2-+S}s z8Q^~iqUUl$gs}u15gRW6in{yQlc%|bv9bOX1i)uw-%SG}QEpeL4qID)IYvq9-s(3C zob=yFItE9?#*R1X$B*_>L}r&oi6=(CpCCYP+ik-r!{+wmx_bzv=cVHi7C?*{E}#^IK$-Zx4*1SEk0SW$39Cg?E35Of z&T-dy>h1z))9?#@2+1-)W-qWzI6P;Z%L2n*RThlOmk&PdI@5gFBVmB{w#JvHLPJC% zG6)cl)e|xan|&BP>SST#rA7}SFu4Baxz1T`9^PE^)aJHhMUz|u3#wBPD3<7LITrGGTYm#tXz2ilni$D2YE2L=?TB7z zIMX5gq_dsY?4D9r{&#z7eXkXY!j9N1 zyn6B|Qv%D+t=Kw%G8pKzX7v81YC0*$lQjiTIo|R(qm|)8cLACSAJPC@1#2<-v|8q} zRg|jHW4%ZqQ}ljEhZ0OWpI`Z53Qt3P7fjiGx>Omzba66{=FQ~?^NYSGyhzBJ#gN0< zA`16HX`d7Y*5EB_!*f7A5{cVUS}e_R+qrgV&zc4?H$^o_&QIMQF+F5h9z zbHv5PFVeVu-@${xxa$p!-Qj;)&JnR-gNIs+oD=*+zP$RaxeC6H_;lA=-(HPEUz-)2 zc^@A$?HvtrdG8Z&PuPHgNU(&P6UQ0n=D1DRc_R09#6NdJrp>qUD(Ot9wvDG1-)%f7 zX`A@fk8dTJ1gVw#^GWAeuu{tj5a|pM`_iNzrsU6+8*;IZEr4o)@`!8CCSt~$9h45y@GcT zQe_4`YVf3q7i}txCKzK-lFGkh*PIY(zE%IW7B+2XKE1r5)*NwN709Q9kv3XaVcwLJ@{Qcp{YM*aDJ=|x7t1h*s+xJeFtMWKq(6PAV?QB7F4-P`4Dct?) z`XnnKNdgGu(`P#m|I_~VS|8~wLtbuPKApJo;}F)5hfH#EXn}X(6~#{&$5Z(%cSu)x zXklnNLRdoJmmefbr)(QjG0a2Y=6*9;R4UfTxINevu`Us4s&>4x*a0wyo+;Ji*pAw5 z!hn~7lP$F)@&XO)*QQM{X{PQ5bV!s)w56hlXG-b-9tfsehBS}OP?JW|{vDC2%3wvN z{HHi`M`Q#(RQ4ND_>aJ_x^Ehi9JPOSBItlWEnQ1F@4IwilIPeM3Q6o=ZwhN-4#k|R P00000NkvXXu0mjfgwxI} literal 0 HcmV?d00001 diff --git a/lib/pages/common/custom_dialog.dart b/lib/pages/common/custom_dialog.dart index a40ef10f..9899bda4 100644 --- a/lib/pages/common/custom_dialog.dart +++ b/lib/pages/common/custom_dialog.dart @@ -12,7 +12,7 @@ Future showCustomDialog({ double? iconWidth, VoidCallback? onOkPressed, bool barrierDismissible = false, - required List actions, + List? actions, }) { return showDialog( context: context, diff --git a/lib/pages/roles_and_permission/model/role_type_model.dart b/lib/pages/roles_and_permission/model/role_type_model.dart index 1705e25c..16b24ec5 100644 --- a/lib/pages/roles_and_permission/model/role_type_model.dart +++ b/lib/pages/roles_and_permission/model/role_type_model.dart @@ -16,7 +16,7 @@ class RoleTypeModel { uuid: json['uuid'], createdAt: json['createdAt'], updatedAt: json['updatedAt'], - type: json['type'], + type: json['type'].toString().toLowerCase().replaceAll("_", " "), ); } } diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart similarity index 73% rename from lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index f0e3c9ad..f0ddf01e 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -1,20 +1,19 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/common/custom_dialog.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart'; -import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_node_model.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/services/user_permission.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; class UsersBloc extends Bloc { UsersBloc() : super(UsersInitial()) { - on(_getUsers); - on(_changeUserStatus); on(isCompleteBasicsFun); on(_onLoadCommunityAndSpaces); on(searchTreeNode); @@ -25,6 +24,7 @@ class UsersBloc extends Bloc { on(_sendInvitUser); on(_validateBasicsStep); on(isCompleteRoleFun); + on(checkEmail); } void _validateBasicsStep(ValidateBasicsStep event, Emitter emit) { if (formKey.currentState?.validate() ?? false) { @@ -34,74 +34,8 @@ class UsersBloc extends Bloc { } } - List users = []; String roleSelected = ''; - Future _getUsers(GetUsers event, Emitter emit) async { - emit(UsersLoadingState()); - try { - users = [ - RolesUserModel( - id: '1', - userName: 'user 1', - userEmail: 'test1@test.com', - action: '', - createdBy: 'Admin', - creationDate: '25/10/2024', - creationTime: '10:30 AM', - status: 'Invited', - ), - RolesUserModel( - id: '2', - userName: 'user 2', - userEmail: 'test2@test.com', - action: '', - createdBy: 'Admin', - creationDate: '25/10/2024', - creationTime: '10:30 AM', - status: 'Active', - ), - RolesUserModel( - id: '3', - userName: 'user 3', - userEmail: 'test3@test.com', - action: '', - createdBy: 'Admin', - creationDate: '25/10/2024', - creationTime: '10:30 AM', - status: 'Disabled', - ), - ]; - emit(UsersLoadedState(users: users)); - } catch (e) { - emit(ErrorState(e.toString())); - } - } - - void _changeUserStatus(ChangeUserStatus event, Emitter emit) { - try { - users = users.map((user) { - if (user.id == event.userId) { - return RolesUserModel( - id: user.id, - userName: user.userName, - userEmail: user.userEmail, - createdBy: user.createdBy, - creationDate: user.creationDate, - creationTime: user.creationTime, - status: event.newStatus, - action: user.action, - ); - } - return user; - }).toList(); - - emit(UsersLoadedState(users: users)); - } catch (e) { - emit(ErrorState(e.toString())); - } - } - final formKey = GlobalKey(); final TextEditingController firstNameController = TextEditingController(); final TextEditingController lastNameController = TextEditingController(); @@ -109,6 +43,9 @@ class UsersBloc extends Bloc { final TextEditingController phoneController = TextEditingController(); final TextEditingController jobTitleController = TextEditingController(); + final TextEditingController roleSearchController = TextEditingController(); + // final TextEditingController jobTitleController = TextEditingController(); + bool? isCompleteBasics; bool? isCompleteRolePermissions; bool? isCompleteSpaces; @@ -117,30 +54,6 @@ class UsersBloc extends Bloc { int numberSpaces = 0; int numberRole = 0; - // isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { - // emit(UsersLoadingState()); - // isCompleteBasics = firstNameController.text.isNotEmpty && - // lastNameController.text.isNotEmpty && - // emailController.text.isNotEmpty; - // emit(ChangeStatusSteps()); - // return isCompleteBasics; - // } - - bool isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { - emit(UsersLoadingState()); - - final emailRegex = RegExp( - r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', - ); - isCompleteBasics = firstNameController.text.isNotEmpty && - lastNameController.text.isNotEmpty && - emailController.text.isNotEmpty && - emailRegex.hasMatch(emailController.text); - - emit(ChangeStatusSteps()); - return isCompleteBasics!; - } - void isCompleteSpacesFun( CheckSpacesStepStatus event, Emitter emit) { emit(UsersLoadingState()); @@ -184,7 +97,7 @@ class UsersBloc extends Bloc { ); }).toList(), ); - emit(ChangeStatusSteps()); + emit(SpacesLoadedState()); return updatedCommunities; } catch (e) { emit(ErrorState('Error loading communities and spaces: $e')); @@ -239,6 +152,7 @@ class UsersBloc extends Bloc { } List selectedIds = []; + List getSelectedIds(List nodes) { List selectedIds = []; for (var node in nodes) { @@ -259,7 +173,7 @@ class UsersBloc extends Bloc { try { emit(UsersLoadingState()); roles = await UserPermissionApi().fetchRoles(); - add(PermissionEvent(roleUuid: roles.first.uuid)); + // add(PermissionEvent(roleUuid: roles.first.uuid)); emit(RolePermissionInitial()); } catch (e) { emit(ErrorState('Error loading communities and spaces: $e')); @@ -294,18 +208,40 @@ class UsersBloc extends Bloc { _sendInvitUser(SendInviteUsers event, Emitter emit) async { try { emit(UsersLoadingState()); - List selectedIds = getSelectedIds(updatedCommunities); - await UserPermissionApi().sendInviteUser( - email: emailController.text, - firstName: firstNameController.text, - jobTitle: jobTitleController.text, - lastName: lastNameController.text, - phoneNumber: phoneController.text, - roleUuid: roleSelected, - spaceUuids: selectedIds); + List selectedIds = getSelectedIds(updatedCommunities) ?? []; + bool res = await UserPermissionApi().sendInviteUser( + email: emailController.text, + firstName: firstNameController.text, + jobTitle: jobTitleController.text, + lastName: lastNameController.text, + phoneNumber: phoneController.text, + roleUuid: roleSelected, + spaceUuids: selectedIds, + ); + if (res == true) { + showCustomDialog( + barrierDismissible: false, + context: event.context, + message: "The invite was sent successfully.", + iconPath: Assets.deviceNoteIcon, + title: "Invite Success", + dialogHeight: MediaQuery.of(event.context).size.height * 0.3, + actions: [ + TextButton( + onPressed: () { + Navigator.of(event.context).pop(); + Navigator.of(event.context).pop(); + }, + child: const Text('OK'), + ), + ], + ); + } else { + emit(const ErrorState('Failed to send invite.')); + } emit(SaveState()); } catch (e) { - emit(ErrorState('Error: $e')); + emit(ErrorState('Failed to send invite: ${e.toString()}')); } } @@ -319,6 +255,37 @@ class UsersBloc extends Bloc { emit(ChangeStatusSteps()); } + String checkEmailValid = ''; + + Future checkEmail( + CheckEmailEvent event, Emitter emit) async { + emit(UsersLoadingState()); + String? res = await UserPermissionApi().checkEmail( + emailController.text, + ); + checkEmailValid = res!; + emit(ChangeStatusSteps()); + } + + bool isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { + emit(UsersLoadingState()); + add(const CheckEmailEvent()); + final emailRegex = RegExp( + r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', + ); + bool isEmailValid = emailRegex.hasMatch(emailController.text); + bool isEmailServerValid = checkEmailValid == 'Valid email'; + isCompleteBasics = firstNameController.text.isNotEmpty && + lastNameController.text.isNotEmpty && + emailController.text.isNotEmpty && + isEmailValid && + isEmailServerValid; + + emit(ChangeStatusSteps()); + emit(ValidateBasics()); + return isCompleteBasics!; + } + void _clearHighlightsRolePermission(List nodes) { for (var node in nodes) { node.isHighlighted = false; diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart similarity index 82% rename from lib/pages/roles_and_permission/users_page/bloc/users_event.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart index 633c1ea5..950726d4 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart @@ -1,21 +1,17 @@ import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_node_model.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; sealed class UsersEvent extends Equatable { const UsersEvent(); } -class GetUsers extends UsersEvent { - const GetUsers(); - @override - List get props => []; -} - class SendInviteUsers extends UsersEvent { - const SendInviteUsers(); + final BuildContext context; + const SendInviteUsers({required this.context}); @override - List get props => []; + List get props => [context]; } class CheckSpacesStepStatus extends UsersEvent { @@ -30,7 +26,6 @@ class CheckRoleStepStatus extends UsersEvent { List get props => []; } - class LoadCommunityAndSpacesEvent extends UsersEvent { const LoadCommunityAndSpacesEvent(); @override @@ -57,16 +52,6 @@ class GetBatchStatus extends UsersEvent { List get props => [uuids]; } -class ChangeUserStatus extends UsersEvent { - final String userId; - final String newStatus; - - const ChangeUserStatus({required this.userId, required this.newStatus}); - - @override - List get props => [userId, newStatus]; -} - class CheckStepStatus extends UsersEvent { final int? steps; const CheckStepStatus({this.steps}); @@ -104,3 +89,9 @@ class ValidateBasicsStep extends UsersEvent { @override List get props => []; } + +class CheckEmailEvent extends UsersEvent { + const CheckEmailEvent(); + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart similarity index 88% rename from lib/pages/roles_and_permission/users_page/bloc/users_status.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart index b5fc01d7..c1bf3512 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart @@ -30,11 +30,10 @@ final class SaveState extends UsersState { List get props => []; } -final class UsersLoadedState extends UsersState { - List users = []; - UsersLoadedState({required this.users}); +final class SpacesLoadedState extends UsersState { + SpacesLoadedState(); @override - List get props => [users]; + List get props => []; } final class ErrorState extends UsersState { @@ -77,3 +76,7 @@ class BasicsStepInvalidState extends UsersState { @override List get props => []; } +final class ValidateBasics extends UsersState { + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart new file mode 100644 index 00000000..c476ebb4 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart @@ -0,0 +1,40 @@ +class PermissionOption { + String id; + String title; + bool isChecked; + bool isHighlighted; + List subOptions; + + PermissionOption({ + required this.id, + required this.title, + this.isChecked = false, + this.isHighlighted = false, + this.subOptions = const [], + }); + + factory PermissionOption.fromJson(Map json) { + return PermissionOption( + id: json['id'] ?? '', + title: json['title'].toString().toLowerCase().replaceAll("_", " ") ?? '', + isChecked: json['isChecked'] ?? false, + isHighlighted: json['isHighlighted'] ?? false, + subOptions: (json['subOptions'] as List?) + ?.map((sub) => PermissionOption.fromJson(sub)) + .toList() ?? + [], + ); + } + + Map toJson() { + return { + 'id': id, + 'title': title, + 'isChecked': isChecked, + 'isHighlighted': isHighlighted, + 'subOptions': subOptions.map((sub) => sub.toJson()).toList(), + }; + } +} + +enum CheckState { none, some, all } diff --git a/lib/pages/roles_and_permission/users_page/model/tree_node_model.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart similarity index 100% rename from lib/pages/roles_and_permission/users_page/model/tree_node_model.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart similarity index 92% rename from lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart index cae89058..14f7a0fb 100644 --- a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/view/basics_view.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/view/spaces_access_view.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -56,7 +56,6 @@ class _AddNewUserDialogState extends State { Expanded( child: Row( children: [ - // Sidebar for Steps Expanded( child: Container( padding: const EdgeInsets.all(20), @@ -75,7 +74,6 @@ class _AddNewUserDialogState extends State { width: 1, color: ColorsManager.grayBorder, ), - // Main content (Form) Expanded( flex: 2, child: Padding( @@ -109,6 +107,8 @@ class _AddNewUserDialogState extends State { ), InkWell( onTap: () { + _blocRole.add(const CheckEmailEvent()); + setState(() { if (currentStep < 3) { currentStep++; @@ -117,11 +117,10 @@ class _AddNewUserDialogState extends State { } else if (currentStep == 3) { _blocRole .add(const CheckSpacesStepStatus()); - _blocRole - .add(const CheckSpacesStepStatus()); - } else { - _blocRole.add(const SendInviteUsers()); } + } else { + _blocRole + .add(SendInviteUsers(context: context)); } }); }, @@ -161,12 +160,84 @@ class _AddNewUserDialogState extends State { } } + int step3 = 0; + + Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + setState(() { + bloc.add(const CheckSpacesStepStatus()); + currentStep = step; + Future.delayed(const Duration(milliseconds: 500), () { + bloc.add(const ValidateBasicsStep()); + }); + }); + + if (step3 == 3) { + bloc.add(const CheckRoleStepStatus()); + } + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + SvgPicture.asset( + currentStep == step + ? Assets.currentProcessIcon + : bloc.isCompleteBasics == false + ? Assets.wrongProcessIcon + : bloc.isCompleteBasics == true + ? Assets.completeProcessIcon + : Assets.uncomplete_ProcessIcon, + width: 25, + height: 25, + ), + const SizedBox(width: 10), + Text( + label, + style: TextStyle( + fontSize: 16, + color: currentStep == step + ? ColorsManager.blackColor + : ColorsManager.greyColor, + fontWeight: currentStep == step + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ], + ), + ), + if (step != 3) + Padding( + padding: const EdgeInsets.all(5.0), + child: Padding( + padding: const EdgeInsets.only(left: 12), + child: Container( + height: 60, + width: 1, + color: Colors.grey, + ), + ), + ) + ], + ), + ); + } + Widget _buildStep2Indicator(int step, String label, UsersBloc bloc) { return GestureDetector( onTap: () { setState(() { currentStep = step; bloc.add(const CheckStepStatus()); + if (step3 == 3) { + bloc.add(const CheckRoleStepStatus()); + } }); }, child: Column( @@ -221,15 +292,14 @@ class _AddNewUserDialogState extends State { ); } - Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) { + Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) { return GestureDetector( onTap: () { setState(() { - bloc.add(const CheckSpacesStepStatus()); currentStep = step; - }); - Future.delayed(const Duration(milliseconds: 500), () { - bloc.add(const ValidateBasicsStep()); + step3 = step; + bloc.add(const CheckSpacesStepStatus()); + bloc.add(const CheckStepStatus()); }); }, child: Column( @@ -243,9 +313,9 @@ class _AddNewUserDialogState extends State { SvgPicture.asset( currentStep == step ? Assets.currentProcessIcon - : bloc.isCompleteBasics == false + : bloc.isCompleteRolePermissions == false ? Assets.wrongProcessIcon - : bloc.isCompleteBasics == true + : bloc.isCompleteRolePermissions == true ? Assets.completeProcessIcon : Assets.uncomplete_ProcessIcon, width: 25, @@ -283,63 +353,4 @@ class _AddNewUserDialogState extends State { ), ); } - - Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) { - return GestureDetector( - onTap: () { - setState(() { - currentStep = step; - bloc.add(const CheckSpacesStepStatus()); - bloc.add(const CheckStepStatus()); - }); - }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(5.0), - child: Row( - children: [ - SvgPicture.asset( - currentStep == step - ? Assets.currentProcessIcon - : bloc.isCompleteRolePermissions == false - ? Assets.wrongProcessIcon - : Assets.uncomplete_ProcessIcon, - width: 25, - height: 25, - ), - const SizedBox(width: 10), - Text( - label, - style: TextStyle( - fontSize: 16, - color: currentStep == step - ? ColorsManager.blackColor - : ColorsManager.greyColor, - fontWeight: currentStep == step - ? FontWeight.bold - : FontWeight.normal, - ), - ), - ], - ), - ), - if (step != 3) - Padding( - padding: const EdgeInsets.all(5.0), - child: Padding( - padding: const EdgeInsets.only(left: 12), - child: Container( - height: 60, - width: 1, - color: Colors.grey, - ), - ), - ) - ], - ), - ); - } } diff --git a/lib/pages/roles_and_permission/users_page/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart similarity index 76% rename from lib/pages/roles_and_permission/users_page/view/basics_view.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart index 22398ed3..a6ec686b 100644 --- a/lib/pages/roles_and_permission/users_page/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:intl_phone_field/countries.dart'; import 'package:intl_phone_field/country_picker_dialog.dart'; import 'package:intl_phone_field/intl_phone_field.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -30,7 +32,7 @@ class BasicsView extends StatelessWidget { color: Colors.black), ), const SizedBox( - height: 80, + height: 50, ), Text( 'To get started, fill out some basic information about who you’re adding as a user.', @@ -40,11 +42,10 @@ class BasicsView extends StatelessWidget { ), ), const SizedBox( - height: 25, + height: 35, ), Row( children: [ - // First Name Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -52,9 +53,6 @@ class BasicsView extends StatelessWidget { SizedBox( child: Row( children: [ - // SizedBox( - // width: 15, - // ), const Text( " * ", style: TextStyle( @@ -75,7 +73,14 @@ class BasicsView extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( - style: TextStyle(color: Colors.black), + style: + const TextStyle(color: ColorsManager.blackColor), + onChanged: (value) { + Future.delayed(const Duration(milliseconds: 200), + () { + _blocRole.add(ValidateBasicsStep()); + }); + }, controller: _blocRole.firstNameController, decoration: inputTextFormDeco( hintText: "Enter first name", @@ -96,10 +101,7 @@ class BasicsView extends StatelessWidget { ], ), ), - - SizedBox(width: 10), - - // Last Name + const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -125,8 +127,14 @@ class BasicsView extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( + onChanged: (value) { + Future.delayed(const Duration(milliseconds: 200), + () { + _blocRole.add(ValidateBasicsStep()); + }); + }, controller: _blocRole.lastNameController, - style: TextStyle(color: Colors.black), + style: const TextStyle(color: Colors.black), decoration: inputTextFormDeco(hintText: "Enter last name") .copyWith( @@ -149,7 +157,7 @@ class BasicsView extends StatelessWidget { ), ], ), - SizedBox(height: 10), + const SizedBox(height: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -176,8 +184,14 @@ class BasicsView extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( + onChanged: (value) { + Future.delayed(const Duration(milliseconds: 200), () { + _blocRole.add(const CheckStepStatus()); + _blocRole.add(ValidateBasicsStep()); + }); + }, controller: _blocRole.emailController, - style: TextStyle(color: Colors.black), + style: const TextStyle(color: ColorsManager.blackColor), decoration: inputTextFormDeco(hintText: "name@example.com") .copyWith( hintStyle: context.textTheme.bodyMedium?.copyWith( @@ -189,24 +203,24 @@ class BasicsView extends StatelessWidget { if (value == null || value.isEmpty) { return 'Enter Email Address'; } - // Regular expression for email validation final emailRegex = RegExp( r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', ); if (!emailRegex.hasMatch(value)) { return 'Enter a valid Email Address'; } + if (_blocRole.checkEmailValid != "Valid email") { + return _blocRole.checkEmailValid; + } return null; }, ), ), ], ), - SizedBox(height: 10), + const SizedBox(height: 10), Row( children: [ - // Phone Number - Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -227,7 +241,8 @@ class BasicsView extends StatelessWidget { pickerDialogStyle: PickerDialogStyle(), dropdownIconPosition: IconPosition.leading, disableLengthCheck: true, - dropdownTextStyle: TextStyle(color: Colors.black), + dropdownTextStyle: + const TextStyle(color: ColorsManager.blackColor), textInputAction: TextInputAction.done, decoration: inputTextFormDeco( hintText: "05x xxx xxxx", @@ -238,18 +253,39 @@ class BasicsView extends StatelessWidget { color: ColorsManager.textGray), ), initialCountryCode: 'AE', - style: TextStyle(color: Colors.black), - onChanged: (phone) { - print(phone.completeNumber); - }, + countries: const [ + Country( + name: "United Arab Emirates", + nameTranslations: { + "en": "United Arab Emirates", + "ar": "الإمارات العربية المتحدة", + }, + flag: "🇦🇪", + code: "AE", + dialCode: "971", + minLength: 9, + maxLength: 9, + ), + Country( + name: "Saudi Arabia", + nameTranslations: { + "en": "Saudi Arabia", + "ar": "السعودية", + }, + flag: "🇸🇦", + code: "SA", + dialCode: "966", + minLength: 9, + maxLength: 9, + ), + ], + style: const TextStyle(color: Colors.black), + controller: _blocRole.phoneController, ) ], ), ), - - SizedBox(width: 10), - - // Job Title + const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -270,7 +306,8 @@ class BasicsView extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: TextFormField( controller: _blocRole.jobTitleController, - style: TextStyle(color: Colors.black), + style: + const TextStyle(color: ColorsManager.blackColor), decoration: inputTextFormDeco( hintText: "Job Title (Optional)") .copyWith( @@ -287,7 +324,7 @@ class BasicsView extends StatelessWidget { ), ], ), - SizedBox(height: 20), + const SizedBox(height: 20), ], ), ); diff --git a/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart similarity index 96% rename from lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart index f10fd4ed..002b0171 100644 --- a/lib/pages/roles_and_permission/users_page/view/delete_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class DeleteUserDialog extends StatefulWidget { diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart new file mode 100644 index 00000000..b16cd8d2 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart @@ -0,0 +1,239 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_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'; + +class PermissionManagement extends StatefulWidget { + final UsersBloc? bloc; + const PermissionManagement({Key? key, this.bloc}) : super(key: key); + + @override + _PermissionManagementState createState() => _PermissionManagementState(); +} + +class _PermissionManagementState extends State { + void toggleOptionById(String id) { + setState(() { + for (var mainOption in widget.bloc!.permissions) { + if (mainOption.id == id) { + final isChecked = + checkifOneOfthemChecked(mainOption) == CheckState.all; + mainOption.isChecked = !isChecked; + + for (var subOption in mainOption.subOptions) { + subOption.isChecked = !isChecked; + for (var child in subOption.subOptions) { + child.isChecked = !isChecked; + } + } + return; + } + + for (var subOption in mainOption.subOptions) { + if (subOption.id == id) { + subOption.isChecked = !subOption.isChecked; + for (var child in subOption.subOptions) { + child.isChecked = subOption.isChecked; + } + mainOption.isChecked = + mainOption.subOptions.every((sub) => sub.isChecked); + return; + } + + for (var child in subOption.subOptions) { + if (child.id == id) { + child.isChecked = !child.isChecked; + subOption.isChecked = + subOption.subOptions.every((child) => child.isChecked); + mainOption.isChecked = + mainOption.subOptions.every((sub) => sub.isChecked); + return; + } + } + } + } + }); + } + + CheckState checkifOneOfthemChecked(PermissionOption mainOption) { + bool allSelected = true; + bool someSelected = false; + + for (var subOption in mainOption.subOptions) { + if (subOption.isChecked) { + someSelected = true; + } else { + allSelected = false; + } + + for (var child in subOption.subOptions) { + if (child.isChecked) { + someSelected = true; + } else { + allSelected = false; + } + } + } + + if (allSelected) { + return CheckState.all; + } else if (someSelected) { + return CheckState.some; + } else { + return CheckState.none; + } + } + + @override + Widget build(BuildContext context) { + return ListView.builder( + padding: const EdgeInsets.all(8), + itemCount: widget.bloc!.permissions.length, + itemBuilder: (context, index) { + final option = widget.bloc!.permissions[index]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + InkWell( + // onTap: () => toggleOptionById(option.id), + child: Builder( + builder: (context) { + final checkState = checkifOneOfthemChecked(option); + + if (checkState == CheckState.all) { + return Image.asset( + Assets.CheckBoxChecked, + width: 20, + height: 20, + ); + } else if (checkState == CheckState.some) { + return Image.asset( + Assets.rectangleCheckBox, + width: 20, + height: 20, + ); + } else { + return Image.asset( + Assets.emptyBox, + width: 20, + height: 20, + ); + } + }, + ), + ), + const SizedBox(width: 8), + Text( + option.title, + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 12, + color: ColorsManager.blackColor), + ), + ], + ), + const SizedBox( + height: 10, + ), + ...option.subOptions.map((subOption) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + color: option.isHighlighted + ? Colors.blue.shade50 + : Colors.white, + child: Row( + children: [ + InkWell( + // onTap: () => toggleOptionById(subOption.id), + child: Builder( + builder: (context) { + final checkState = + checkifOneOfthemChecked(PermissionOption( + id: subOption.id, + title: subOption.title, + subOptions: [subOption], + )); + + if (checkState == CheckState.all) { + return Image.asset( + Assets.CheckBoxChecked, + width: 20, + height: 20, + ); + } else if (checkState == CheckState.some) { + return Image.asset( + Assets.rectangleCheckBox, + width: 20, + height: 20, + ); + } else { + return Image.asset( + Assets.emptyBox, + width: 20, + height: 20, + ); + } + }, + ), + ), + const SizedBox(width: 8), + Text( + subOption.title, + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 12, + color: ColorsManager.lightGreyColor), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only(left: 50.0), + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 2.0, + crossAxisSpacing: 0.2, + childAspectRatio: 5, + ), + itemCount: subOption.subOptions.length, + itemBuilder: (context, index) { + final child = subOption.subOptions[index]; + return CheckboxListTile( + selectedTileColor: child.isHighlighted + ? Colors.blue.shade50 + : Colors.white, + dense: true, + controlAffinity: ListTileControlAffinity.leading, + title: Text( + child.title, + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.lightGreyColor), + ), + value: child.isChecked, + onChanged: (value) => toggleOptionById(child.id), + enabled: false, + ); + }, + ), + ) + ], + ); + }).toList(), + ], + ); + }, + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart new file mode 100644 index 00000000..1bc9331e --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class RoleDropdown extends StatefulWidget { + final UsersBloc? bloc; + const RoleDropdown({super.key, this.bloc}); + + @override + _RoleDropdownState createState() => _RoleDropdownState(); +} + +class _RoleDropdownState extends State { + late String selectedRole; + + @override + void initState() { + super.initState(); + selectedRole = widget.bloc!.roleSelected; + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Row( + children: [ + Text( + " * ", + style: TextStyle( + color: ColorsManager.red, + fontWeight: FontWeight.w900, + fontSize: 15, + ), + ), + Text( + "Role", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: Colors.black, + ), + ), + ], + ), + const SizedBox(height: 8), + SizedBox( + child: DropdownButtonFormField( + dropdownColor: ColorsManager.whiteColors, + alignment: Alignment.center, + focusColor: Colors.white, + autofocus: true, + value: selectedRole.isNotEmpty ? selectedRole : null, + items: widget.bloc!.roles.map((role) { + return DropdownMenuItem( + value: role.uuid, + child: Text(role.type), + ); + }).toList(), + onChanged: (value) { + setState(() { + selectedRole = value!; + }); + widget.bloc!.roleSelected = selectedRole; + widget.bloc! + .add(PermissionEvent(roleUuid: widget.bloc!.roleSelected)); + }, + icon: const SizedBox.shrink(), + borderRadius: const BorderRadius.all(Radius.circular(10)), + hint: const Padding( + padding: EdgeInsets.only(left: 10), + child: Text( + "Please Select", + style: TextStyle( + color: ColorsManager.textGray, + ), + ), + ), + decoration: inputTextFormDeco().copyWith( + contentPadding: EdgeInsets.zero, + suffixIcon: Container( + padding: EdgeInsets.zero, + width: 70, + height: 45, + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: const BorderRadius.only( + bottomRight: Radius.circular(10), + topRight: Radius.circular(10), + ), + border: Border.all( + color: ColorsManager.textGray, + width: 1.0, + ), + ), + child: const Center( + child: Icon( + Icons.keyboard_arrow_down, + color: ColorsManager.textGray, + ), + ), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart new file mode 100644 index 00000000..b054a88c --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.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 RolesAndPermission extends StatelessWidget { + const RolesAndPermission({super.key}); + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + + return BlocBuilder(builder: (context, state) { + final _blocRole = BlocProvider.of(context); + return Container( + color: Colors.white, + child: Form( + key: _blocRole.formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Role & Permissions', + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 20, + color: Colors.black), + ), + const SizedBox( + height: 15, + ), + SizedBox( + width: 350, + height: 100, + child: RoleDropdown( + bloc: _blocRole, + )), + const SizedBox(height: 10), + Expanded( + child: SizedBox( + child: Column( + children: [ + Expanded( + flex: 2, + child: 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), + controller: + _blocRole.roleSearchController, + onChanged: (value) { + _blocRole.add(SearchPermission( + nodes: _blocRole.permissions, + searchTerm: 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), + ), + ), + ), + ), + ], + ), + ), + ), + ), + Expanded( + flex: 7, + child: Container( + color: ColorsManager.CircleRolesBackground, + padding: const EdgeInsets.all(8.0), + child: Container( + color: ColorsManager.whiteColors, + child: PermissionManagement( + bloc: _blocRole, + )))) + ], + ), + ), + ), + ], + ), + ), + ); + }); + } +} diff --git a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart similarity index 98% rename from lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart rename to lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart index 4bc330b2..f942d1d5 100644 --- a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/model/tree_node_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_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'; diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart new file mode 100644 index 00000000..2a31a91f --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart @@ -0,0 +1,185 @@ +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart'; + +class UserTableBloc extends Bloc { + UserTableBloc() : super(TableInitial()) { + on(_getUsers); + on(_changeUserStatus); + on(_toggleSortUsersByNameAsc); + on(_toggleSortUsersByNameDesc); + on(_toggleSortUsersByDateOldestToNewest); + on(_toggleSortUsersByDateNewestToOldest); + } + + List users = []; + List initialUsers = []; // Save the initial state + String currentSortOrder = ''; // Keeps track of the current sorting order + String currentSortOrderDate = ''; // Keeps track of the current sorting order + + Future _getUsers(GetUsers event, Emitter emit) async { + emit(UsersLoadingState()); + try { + users = [ + RolesUserModel( + id: '1', + userName: 'b 1', + userEmail: 'test1@test.com', + action: '', + createdBy: 'Admin', + creationDate: '25/10/2024', + creationTime: '10:30 AM', + status: 'Invited', + ), + RolesUserModel( + id: '2', + userName: 'a 2', + userEmail: 'test2@test.com', + action: '', + createdBy: 'Admin', + creationDate: '24/10/2024', + creationTime: '2:30 PM', + status: 'Active', + ), + RolesUserModel( + id: '3', + userName: 'c 3', + userEmail: 'test3@test.com', + action: '', + createdBy: 'Admin', + creationDate: '23/10/2024', + creationTime: '9:00 AM', + status: 'Disabled', + ), + ]; + // Sort users by newest to oldest as default + users.sort((a, b) { + final dateA = _parseDateTime(a.creationDate!); + final dateB = _parseDateTime(b.creationDate!); + return dateB.compareTo(dateA); // Newest to oldest + }); + initialUsers = List.from(users); // Save the initial state + emit(UsersLoadedState(users: users)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + void _changeUserStatus(ChangeUserStatus event, Emitter emit) { + try { + users = users.map((user) { + if (user.id == event.userId) { + return RolesUserModel( + id: user.id, + userName: user.userName, + userEmail: user.userEmail, + createdBy: user.createdBy, + creationDate: user.creationDate, + creationTime: user.creationTime, + status: event.newStatus, + action: user.action, + ); + } + return user; + }).toList(); + + emit(UsersLoadedState(users: users)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + void _toggleSortUsersByNameAsc( + SortUsersByNameAsc event, Emitter emit) { + if (currentSortOrder == "Asc") { + // If already sorted ascending, reset to the initial state + emit(UsersLoadingState()); + currentSortOrder = ""; + users = List.from(initialUsers); // Reset to saved initial state + emit(UsersLoadedState(users: users)); + } else { + // Sort ascending + emit(UsersLoadingState()); + currentSortOrder = "Asc"; + users.sort((a, b) => a.userName!.compareTo(b.userName!)); + emit(UsersLoadedState(users: users)); + } + } + + void _toggleSortUsersByNameDesc( + SortUsersByNameDesc event, Emitter emit) { + if (currentSortOrder == "Desc") { + // If already sorted descending, reset to the initial state + emit(UsersLoadingState()); + currentSortOrder = ""; + users = List.from(initialUsers); // Reset to saved initial state + emit(UsersLoadedState(users: users)); + } else { + // Sort descending + emit(UsersLoadingState()); + currentSortOrder = "Desc"; + users.sort((a, b) => b.userName!.compareTo(a.userName!)); + emit(UsersLoadedState(users: users)); + } + } + + void _toggleSortUsersByDateNewestToOldest( + DateNewestToOldestEvent event, Emitter emit) { + if (currentSortOrderDate == "NewestToOldest") { + // If already sorted ascending, reset to the initial state + emit(UsersLoadingState()); + currentSortOrder = ""; + currentSortOrderDate = ""; + users = List.from(initialUsers); // Reset to saved initial state + emit(UsersLoadedState(users: users)); + } else { + // Sort ascending + emit(UsersLoadingState()); + users.sort((a, b) { + final dateA = _parseDateTime(a.creationDate!); + final dateB = _parseDateTime(b.creationDate!); + return dateB.compareTo(dateA); // Newest to oldest + }); + emit(UsersLoadedState(users: users)); + } + } + + void _toggleSortUsersByDateOldestToNewest( + DateOldestToNewestEvent event, Emitter emit) { + if (currentSortOrderDate == "OldestToNewest") { + // If already sorted ascending, reset to the initial state + emit(UsersLoadingState()); + currentSortOrder = ""; + currentSortOrderDate = ""; + users = List.from(initialUsers); // Reset to saved initial state + emit(UsersLoadedState(users: users)); + } else { + // Sort ascending + emit(UsersLoadingState()); + users.sort((a, b) { + final dateA = _parseDateTime(a.creationDate!); + final dateB = _parseDateTime(b.creationDate!); + return dateA.compareTo(dateB); // Newest to oldest + }); + emit(UsersLoadedState(users: users)); + } + } + + DateTime _parseDateTime(String date) { + try { + // Split the date into day, month, and year + final dateParts = date.split('/'); + final day = int.parse(dateParts[0]); + final month = int.parse(dateParts[1]); + final year = int.parse(dateParts[2]); + + // Split the time into hours and minutes + + return DateTime(year, month, day); + } catch (e) { + throw FormatException('Invalid date or time format: $date '); + } + } +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart new file mode 100644 index 00000000..a6c77bd3 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart @@ -0,0 +1,63 @@ +import 'package:equatable/equatable.dart'; + +sealed class UserTableEvent extends Equatable { + const UserTableEvent(); +} + +class GetRoles extends UserTableEvent { + const GetRoles(); + @override + List get props => []; +} + +class GetUsers extends UserTableEvent { + const GetUsers(); + @override + List get props => []; +} + +class ChangeUserStatus extends UserTableEvent { + final String userId; + final String newStatus; + + const ChangeUserStatus({required this.userId, required this.newStatus}); + + @override + List get props => [userId, newStatus]; +} + +class SortUsersByNameAsc extends UserTableEvent { + const SortUsersByNameAsc(); + + @override + List get props => []; +} + +class SortUsersByNameDesc extends UserTableEvent { + const SortUsersByNameDesc(); + + @override + List get props => []; +} + +class StoreUsersEvent extends UserTableEvent { + const StoreUsersEvent(); + + @override + List get props => []; +} + + +class DateNewestToOldestEvent extends UserTableEvent { + const DateNewestToOldestEvent(); + + @override + List get props => []; +} + +class DateOldestToNewestEvent extends UserTableEvent { + const DateOldestToNewestEvent(); + + @override + List get props => []; +} \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart new file mode 100644 index 00000000..2a132947 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart @@ -0,0 +1,82 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; + +sealed class UserTableState extends Equatable { + const UserTableState(); +} + +final class TableInitial extends UserTableState { + @override + List get props => []; +} + +final class RolesLoadingState extends UserTableState { + @override + List get props => []; +} + +final class UsersLoadingState extends UserTableState { + @override + List get props => []; +} + +final class RolesLoadedState extends UserTableState { + @override + List get props => []; +} + +final class UsersLoadedState extends UserTableState { + List users = []; + UsersLoadedState({required this.users}); + @override + List get props => [users]; +} + +final class ErrorState extends UserTableState { + final String message; + + const ErrorState(this.message); + + @override + List get props => [message]; +} + +/// report state +final class SosReportLoadingState extends UserTableState { + @override + List get props => []; +} + +final class RolesErrorState extends UserTableState { + final String message; + + const RolesErrorState(this.message); + + @override + List get props => [message]; +} + +/// automation reports + +final class SosAutomationReportLoadingState extends UserTableState { + @override + List get props => []; +} + +final class SosAutomationReportErrorState extends UserTableState { + final String message; + + const SosAutomationReportErrorState(this.message); + + @override + List get props => [message]; +} + +final class ChangeTapStatus extends UserTableState { + bool select = true; + + ChangeTapStatus({required this.select}); + + @override + List get props => [select]; +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart b/lib/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart new file mode 100644 index 00000000..45ebc3ae --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +Future showDateFilterMenu({ + required BuildContext context, + Function()? aToZTap, + Function()? zToaTap, + String? isSelected, +}) async { + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; + final RelativeRect position = RelativeRect.fromRect( + Rect.fromLTRB( + overlay.size.width / 2, + 240, + 0, + overlay.size.height, + ), + Offset.zero & overlay.size, + ); + + await showMenu( + context: context, + position: position, + color: ColorsManager.whiteColors, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + bottomRight: Radius.circular(10), + bottomLeft: Radius.circular(10), + ), + ), + items: [ + PopupMenuItem( + onTap: aToZTap, + child: ListTile( + leading: Image.asset( + Assets.AtoZIcon, + width: 25, + ), + title: Text( + "Sort from newest to oldest", + // style: context.textTheme.bodyMedium, + style: TextStyle( + color: isSelected == "NewestToOldest" + ? Colors.black + : Colors.blueGrey), + ), + ), + ), + PopupMenuItem( + onTap: zToaTap, + child: ListTile( + leading: Image.asset( + Assets.ZtoAIcon, + width: 25, + ), + title: Text( + "Sort from oldest to newest", + style: TextStyle( + color: isSelected == "OldestToNewest" + ? Colors.black + : Colors.blueGrey), + ), + ), + ), + ], + ).then((value) { + // setState(() { + // _isDropdownOpen = false; + // }); + }); +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart b/lib/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart new file mode 100644 index 00000000..e78eae6b --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +Future showDeActivateFilterMenu({ + required BuildContext context, + Function()? aToZTap, + Function()? zToaTap, + String? isSelected, +}) async { + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; + final RelativeRect position = RelativeRect.fromRect( + Rect.fromLTRB( + overlay.size.width / 2, + 240, + 0, + overlay.size.height, + ), + Offset.zero & overlay.size, + ); + + await showMenu( + context: context, + position: position, + color: ColorsManager.whiteColors, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + bottomRight: Radius.circular(10), + bottomLeft: Radius.circular(10), + ), + ), + items: [ + PopupMenuItem( + onTap: aToZTap, + child: ListTile( + leading: Image.asset( + Assets.AtoZIcon, + width: 25, + ), + title: Text( + "Sort A to Z", + // style: context.textTheme.bodyMedium, + style: TextStyle( + color: isSelected == "NewestToOldest" + ? Colors.black + : Colors.blueGrey), + ), + ), + ), + PopupMenuItem( + onTap: zToaTap, + child: ListTile( + leading: Image.asset( + Assets.ZtoAIcon, + width: 25, + ), + title: Text( + "Sort Z to A", + style: TextStyle( + color: isSelected == "OldestToNewest" + ? Colors.black + : Colors.blueGrey), + ), + ), + ), + ], + ).then((value) { + // setState(() { + // _isDropdownOpen = false; + // }); + }); +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/name_filter.dart b/lib/pages/roles_and_permission/users_page/users_table/view/name_filter.dart new file mode 100644 index 00000000..e869e10b --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/view/name_filter.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +Future showNameMenu({ + required BuildContext context, + Function()? aToZTap, + Function()? zToaTap, + String? isSelected, +}) async { + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; + final RelativeRect position = RelativeRect.fromRect( + Rect.fromLTRB( + overlay.size.width / 25, + 240, + 0, + overlay.size.height, + ), + Offset.zero & overlay.size, + ); + + await showMenu( + context: context, + position: position, + color: ColorsManager.whiteColors, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + bottomRight: Radius.circular(10), + bottomLeft: Radius.circular(10), + ), + ), + items: [ + PopupMenuItem( + onTap: aToZTap, + child: ListTile( + leading: Image.asset( + Assets.AtoZIcon, + width: 25, + ), + title: Text( + "Sort A to Z", + // style: context.textTheme.bodyMedium, + style: TextStyle( + color: isSelected == "Asc" ? Colors.black : Colors.blueGrey), + ), + ), + ), + PopupMenuItem( + onTap: zToaTap, + child: ListTile( + leading: Image.asset( + Assets.ZtoAIcon, + width: 25, + ), + title: Text( + "Sort Z to A", + style: TextStyle( + color: isSelected == "Desc" ? Colors.black : Colors.blueGrey), + ), + ), + ), + ], + ).then((value) { + // setState(() { + // _isDropdownOpen = false; + // }); + }); +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart new file mode 100644 index 00000000..926ac92a --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart @@ -0,0 +1,299 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class DynamicTableScreen extends StatefulWidget { + final List titles; + final List> rows; + final void Function(int columnIndex)? onFilter; + + DynamicTableScreen( + {required this.titles, required this.rows, required this.onFilter}); + + @override + _DynamicTableScreenState createState() => _DynamicTableScreenState(); +} + +class _DynamicTableScreenState extends State + with WidgetsBindingObserver { + late List columnWidths; + + // @override + // void initState() { + // super.initState(); + // // Initialize column widths with default sizes proportional to the screen width + // // Assigning placeholder values here. The actual sizes will be updated in `build`. + // } + @override + void initState() { + super.initState(); + columnWidths = List.filled(widget.titles.length, 150.0); + WidgetsBinding.instance.addObserver(this); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeMetrics() { + super.didChangeMetrics(); + // Screen size might have changed + final newScreenWidth = MediaQuery.of(context).size.width; + setState(() { + columnWidths = List.generate(widget.titles.length, (index) { + if (index == 1) { + return newScreenWidth * + 0.12; // 20% of screen width for the second column + } else if (index == 9) { + return newScreenWidth * + 0.2; // 25% of screen width for the tenth column + } + return newScreenWidth * + 0.09; // Default to 10% of screen width for other columns + }); + }); + } + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + // Initialize column widths if they are still set to placeholder values + if (columnWidths.every((width) => width == 120.0)) { + columnWidths = List.generate(widget.titles.length, (index) { + if (index == 1) { + return screenWidth * 0.11; + } else if (index == 9) { + return screenWidth * 0.2; + } + return screenWidth * 0.11; + }); + setState(() {}); + } + return Container( + child: SingleChildScrollView( + clipBehavior: Clip.none, + scrollDirection: Axis.horizontal, + child: Container( + decoration: containerDecoration.copyWith( + borderRadius: const BorderRadius.all(Radius.circular(20))), + child: FittedBox( + child: Column( + children: [ + // Header Row with Resizable Columns + Container( + decoration: containerDecoration.copyWith( + color: ColorsManager.CircleRolesBackground, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(15), + topRight: Radius.circular(15))), + child: Row( + children: List.generate(widget.titles.length, (index) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + FittedBox( + child: Container( + padding: const EdgeInsets.only(left: 5, right: 5), + width: columnWidths[index], + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + child: Text( + widget.titles[index], + maxLines: 2, + style: const TextStyle( + overflow: TextOverflow.ellipsis, + fontWeight: FontWeight.w400, + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), + ), + if (index != 1 && + index != 9 && + index != 7 && + index != 5) + FittedBox( + child: IconButton( + icon: SvgPicture.asset( + Assets.filterTableIcon, + fit: BoxFit.none, + ), + onPressed: () { + if (widget.onFilter != null) { + widget.onFilter!(index); + } + }, + ), + ) + ], + ), + ), + ), + GestureDetector( + onHorizontalDragUpdate: (details) { + setState(() { + columnWidths[index] = (columnWidths[index] + + details.delta.dx) + .clamp( + 150.0, 300.0); // Minimum & Maximum size + }); + }, + child: MouseRegion( + cursor: SystemMouseCursors + .resizeColumn, // Set the cursor to resize + child: Container( + color: Colors.green, + child: Container( + color: ColorsManager.boxDivider, + width: 1, + height: 50, // Height of the header cell + ), + ), + ), + ), + ], + ); + }), + ), + ), + // Data Rows with Dividers + Container( + decoration: containerDecoration.copyWith( + color: ColorsManager.whiteColors, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(15), + bottomRight: Radius.circular(15))), + child: Column( + children: widget.rows.map((row) { + int rowIndex = widget.rows.indexOf(row); + return Column( + children: [ + Container( + child: Padding( + padding: const EdgeInsets.only( + left: 5, top: 10, right: 5, bottom: 10), + child: Row( + children: List.generate(row.length, (index) { + return SizedBox( + width: columnWidths[index], + child: SizedBox( + child: Padding( + padding: const EdgeInsets.only( + left: 15, right: 10), + child: row[index], + ), + ), + ); + }), + ), + ), + ), + if (rowIndex < widget.rows.length - 1) + Row( + children: List.generate(widget.titles.length, + (index) { + return SizedBox( + width: columnWidths[index], + child: const Divider( + color: ColorsManager.boxDivider, + thickness: 1, + height: 1, + ), + ); + })) + // Add a Divider below each row except the last one + ], + ); + }).toList(), + ), + ), + ], + ), + ), + ), + ), + ); + } +} + + + + // Widget build(BuildContext context) { + // return Scaffold( + // body: SingleChildScrollView( + // scrollDirection: Axis.horizontal, + // child: SingleChildScrollView( + // scrollDirection: Axis.vertical, + // child: Column( + // children: [ + // // Header Row with Resizable Columns + // Container( + // color: Colors.green, + // child: Row( + // children: List.generate(widget.titles.length, (index) { + // return Row( + // children: [ + // Container( + // width: columnWidths[index], + // decoration: const BoxDecoration( + // color: Colors.green, + // ), + // child: Text( + // widget.titles[index], + // style: TextStyle(fontWeight: FontWeight.bold), + // textAlign: TextAlign.center, + // ), + // ), + // GestureDetector( + // onHorizontalDragUpdate: (details) { + // setState(() { + // columnWidths[index] = (columnWidths[index] + + // details.delta.dx) + // .clamp(50.0, 300.0); // Minimum & Maximum size + // }); + // }, + // child: MouseRegion( + // cursor: SystemMouseCursors + // .resizeColumn, // Set the cursor to resize + // child: Container( + // color: Colors.green, + // child: Container( + // color: Colors.black, + // width: 1, + // height: 50, // Height of the header cell + // ), + // ), + // ), + // ), + // ], + // ); + // }), + // ), + // ), + // // Data Rows + // ...widget.rows.map((row) { + // return Row( + // children: List.generate(row.length, (index) { + // return Container( + // width: columnWidths[index], + // child: row[index], + // ); + // }), + // ); + // }).toList(), + // ], + // ), + // ), + // ), + // ); + // } \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/view/users_page.dart b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart similarity index 73% rename from lib/pages/roles_and_permission/users_page/view/users_page.dart rename to lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart index 273b8a34..758f062b 100644 --- a/lib/pages/roles_and_permission/users_page/view/users_page.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart @@ -1,11 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/view/add_user_dialog.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/view/user_table.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/name_filter.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/user_table.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'; @@ -85,7 +88,7 @@ class UsersPage extends StatelessWidget { ? 'Invited' : 'Active'; context - .read() + .read() .add(ChangeUserStatus(userId: userId, newStatus: newStatus)); }, child: Padding( @@ -104,12 +107,10 @@ class UsersPage extends StatelessWidget { ); } -// return RolesAndPermission(); -// } -// } - return BlocBuilder( + return BlocBuilder( builder: (context, state) { final screenSize = MediaQuery.of(context).size; + final _blocRole = BlocProvider.of(context); if (state is UsersLoadingState) { return const Center(child: CircularProgressIndicator()); @@ -158,7 +159,9 @@ class UsersPage extends StatelessWidget { return const AddNewUserDialog(); }, ).then((listDevice) { - if (listDevice != null) {} + if (listDevice != null) { + + } }); }, child: Container( @@ -181,6 +184,56 @@ class UsersPage extends StatelessWidget { ), const SizedBox(height: 25), DynamicTableScreen( + onFilter: (columnIndex) { + if (columnIndex == 0) { + showNameMenu( + context: context, + isSelected: _blocRole.currentSortOrder, + aToZTap: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + zToaTap: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + if (columnIndex == 4) { + showDateFilterMenu( + context: context, + isSelected: _blocRole.currentSortOrderDate, + aToZTap: () { + context + .read() + .add(const DateNewestToOldestEvent()); + }, + zToaTap: () { + context + .read() + .add(const DateOldestToNewestEvent()); + }, + ); + } + if (columnIndex == 8) { + showDeActivateFilterMenu( + context: context, + isSelected: _blocRole.currentSortOrderDate, + aToZTap: () { + context + .read() + .add(const DateNewestToOldestEvent()); + }, + zToaTap: () { + context + .read() + .add(const DateOldestToNewestEvent()); + }, + ); + } + }, titles: const [ "Full Name", "Email Address", @@ -210,10 +263,10 @@ class UsersPage extends StatelessWidget { status(status: user.status!), Row( children: [ - actionButton( - title: "Activity Log", - onTap: () {}, - ), + // actionButton( + // title: "Activity Log", + // onTap: () {}, + // ), actionButton( title: "Edit", onTap: () {}, diff --git a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart deleted file mode 100644 index 9b7ab542..00000000 --- a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart +++ /dev/null @@ -1,489 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.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 RolesAndPermission extends StatelessWidget { - const RolesAndPermission({super.key}); - @override - Widget build(BuildContext context) { - final size = MediaQuery.of(context).size; - - return BlocBuilder(builder: (context, state) { - final _blocRole = BlocProvider.of(context); - return Container( - color: Colors.white, - child: Form( - key: _blocRole.formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - 'Role & Permissions', - style: context.textTheme.bodyLarge?.copyWith( - fontWeight: FontWeight.w700, - fontSize: 20, - color: Colors.black), - ), - const SizedBox( - height: 15, - ), - SizedBox( - width: 300, - height: 110, - child: DropdownExample( - bloc: _blocRole, - )), - const SizedBox(height: 10), - Expanded( - child: SizedBox( - child: Column( - children: [ - Expanded( - flex: 2, - child: 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), - controller: _blocRole.firstNameController, - onChanged: (value) { - - _blocRole.add(SearchPermission( - nodes: _blocRole.permissions, - searchTerm: 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), - ), - ), - ), - ), - ], - ), - ), - ), - ), - Expanded( - flex: 7, - child: Container( - color: ColorsManager.CircleRolesBackground, - padding: const EdgeInsets.all(8.0), - child: Container( - color: ColorsManager.whiteColors, - child: DeviceManagement( - bloc: _blocRole, - )))) - ], - ), - ), - ), - ], - ), - ), - ); - }); - } -} - -class DropdownExample extends StatefulWidget { - final UsersBloc? bloc; - const DropdownExample({super.key, this.bloc}); - - @override - _DropdownExampleState createState() => _DropdownExampleState(); -} - -class _DropdownExampleState extends State { - String? selectedRole; - @override - void initState() { - super.initState(); - if (widget.bloc != null && widget.bloc!.roles.isNotEmpty) { - selectedRole = widget.bloc!.roles.first.uuid; - } - } - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(10.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - "Role", - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - color: Colors.black, - ), - ), - const SizedBox(height: 8), - SizedBox( - child: DropdownButtonFormField( - alignment: Alignment.center, - focusColor: Colors.white, - autofocus: true, - value: selectedRole, - items: widget.bloc!.roles.map((role) { - return DropdownMenuItem( - value: role.uuid, - child: Text(role.type), - ); - }).toList(), - onChanged: (value) { - setState(() { - selectedRole = value; - }); - widget.bloc!.add(PermissionEvent(roleUuid: selectedRole)); - }, - padding: EdgeInsets.zero, - icon: const SizedBox.shrink(), - borderRadius: const BorderRadius.all(Radius.circular(10)), - hint: const Padding( - padding: EdgeInsets.only(left: 20), - child: Text("Please Select"), - ), - decoration: inputTextFormDeco().copyWith( - contentPadding: EdgeInsets.zero, - suffixIcon: Container( - padding: EdgeInsets.zero, - width: 70, - height: 50, - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: const BorderRadius.only( - bottomRight: Radius.circular(10), - topRight: Radius.circular(10), - ), - border: Border.all( - color: Colors.grey, - width: 1.0, - ), - ), - child: const Center( - child: Icon(Icons.keyboard_arrow_down), - ), - ), - ), - ), - ), - ], - ), - ); - } -} - -class DeviceManagement extends StatefulWidget { - final UsersBloc? bloc; - const DeviceManagement({Key? key, this.bloc}) : super(key: key); - - @override - _DeviceManagementState createState() => _DeviceManagementState(); -} - -class _DeviceManagementState extends State { - void toggleOptionById(String id) { - setState(() { - for (var mainOption in widget.bloc!.permissions) { - if (mainOption.id == id) { - final isChecked = - checkifOneOfthemChecked(mainOption) == CheckState.all; - mainOption.isChecked = !isChecked; - - for (var subOption in mainOption.subOptions) { - subOption.isChecked = !isChecked; - for (var child in subOption.subOptions) { - child.isChecked = !isChecked; - } - } - return; - } - - for (var subOption in mainOption.subOptions) { - if (subOption.id == id) { - subOption.isChecked = !subOption.isChecked; - for (var child in subOption.subOptions) { - child.isChecked = subOption.isChecked; - } - mainOption.isChecked = - mainOption.subOptions.every((sub) => sub.isChecked); - return; - } - - for (var child in subOption.subOptions) { - if (child.id == id) { - child.isChecked = !child.isChecked; - subOption.isChecked = - subOption.subOptions.every((child) => child.isChecked); - mainOption.isChecked = - mainOption.subOptions.every((sub) => sub.isChecked); - return; - } - } - } - } - }); - } - - CheckState checkifOneOfthemChecked(PermissionOption mainOption) { - bool allSelected = true; - bool someSelected = false; - - for (var subOption in mainOption.subOptions) { - if (subOption.isChecked) { - someSelected = true; - } else { - allSelected = false; - } - - for (var child in subOption.subOptions) { - if (child.isChecked) { - someSelected = true; - } else { - allSelected = false; - } - } - } - - if (allSelected) { - return CheckState.all; - } else if (someSelected) { - return CheckState.some; - } else { - return CheckState.none; - } - } - - @override - Widget build(BuildContext context) { - return ListView.builder( - padding: const EdgeInsets.all(8), - itemCount: widget.bloc!.permissions.length, - itemBuilder: (context, index) { - final option = widget.bloc!.permissions[index]; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - InkWell( - // onTap: () => toggleOptionById(option.id), - child: Builder( - builder: (context) { - final checkState = checkifOneOfthemChecked(option); - - if (checkState == CheckState.all) { - return Image.asset( - Assets.CheckBoxChecked, - width: 20, - height: 20, - ); - } else if (checkState == CheckState.some) { - return Image.asset( - Assets.rectangleCheckBox, - width: 20, - height: 20, - ); - } else { - return Image.asset( - Assets.emptyBox, - width: 20, - height: 20, - ); - } - }, - ), - ), - const SizedBox(width: 8), - Text( - option.title, - style: context.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w700, - fontSize: 12, - color: ColorsManager.blackColor), - ), - ], - ), - const SizedBox( - height: 10, - ), - ...option.subOptions.map((subOption) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - color: option.isHighlighted - ? Colors.blue.shade50 - : Colors.white, - child: Row( - children: [ - InkWell( - // onTap: () => toggleOptionById(subOption.id), - child: Builder( - builder: (context) { - final checkState = - checkifOneOfthemChecked(PermissionOption( - id: subOption.id, - title: subOption.title, - subOptions: [subOption], - )); - - if (checkState == CheckState.all) { - return Image.asset( - Assets.CheckBoxChecked, - width: 20, - height: 20, - ); - } else if (checkState == CheckState.some) { - return Image.asset( - Assets.rectangleCheckBox, - width: 20, - height: 20, - ); - } else { - return Image.asset( - Assets.emptyBox, - width: 20, - height: 20, - ); - } - }, - ), - ), - const SizedBox(width: 8), - Text( - subOption.title, - style: context.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w700, - fontSize: 12, - color: ColorsManager.lightGreyColor), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.only(left: 50.0), - child: GridView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - mainAxisSpacing: 2.0, - crossAxisSpacing: 0.2, - childAspectRatio: 5, - ), - itemCount: subOption.subOptions.length, - itemBuilder: (context, index) { - final child = subOption.subOptions[index]; - return CheckboxListTile( - selectedTileColor: child.isHighlighted - ? Colors.blue.shade50 - : Colors.white, - dense: true, - controlAffinity: ListTileControlAffinity.leading, - title: Text( - child.title, - style: context.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w400, - fontSize: 12, - color: ColorsManager.lightGreyColor), - ), - value: child.isChecked, - onChanged: (value) => toggleOptionById(child.id), - enabled: false, - ); - }, - ), - ) - ], - ); - }).toList(), - ], - ); - }, - ); - } -} - - -enum CheckState { none, some, all } - -class PermissionOption { - String id; - String title; - bool isChecked; - bool isHighlighted; - List subOptions; - - PermissionOption({ - required this.id, - required this.title, - this.isChecked = false, - this.isHighlighted = false, - this.subOptions = const [], - }); - - factory PermissionOption.fromJson(Map json) { - return PermissionOption( - id: json['id'] ?? '', - title: json['title'] ?? '', - isChecked: json['isChecked'] ?? false, - isHighlighted: json['isHighlighted'] ?? false, - subOptions: (json['subOptions'] as List?) - ?.map((sub) => PermissionOption.fromJson(sub)) - .toList() ?? - [], - ); - } - - Map toJson() { - return { - 'id': id, - 'title': title, - 'isChecked': isChecked, - 'isHighlighted': isHighlighted, - 'subOptions': subOptions.map((sub) => sub.toJson()).toList(), - }; - } -} diff --git a/lib/pages/roles_and_permission/users_page/view/user_table.dart b/lib/pages/roles_and_permission/users_page/view/user_table.dart deleted file mode 100644 index f951f06e..00000000 --- a/lib/pages/roles_and_permission/users_page/view/user_table.dart +++ /dev/null @@ -1,256 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; - -class DynamicTableScreen extends StatefulWidget { - final List titles; - final List> rows; - - DynamicTableScreen({required this.titles, required this.rows}); - - @override - _DynamicTableScreenState createState() => _DynamicTableScreenState(); -} - -class _DynamicTableScreenState extends State - with WidgetsBindingObserver { - late List columnWidths; - - // @override - // void initState() { - // super.initState(); - // // Initialize column widths with default sizes proportional to the screen width - // // Assigning placeholder values here. The actual sizes will be updated in `build`. - // } - @override - void initState() { - super.initState(); - columnWidths = List.filled(widget.titles.length, 150.0); - - WidgetsBinding.instance.addObserver(this); - } - - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } - - @override - void didChangeMetrics() { - super.didChangeMetrics(); - // Screen size might have changed - final newScreenWidth = MediaQuery.of(context).size.width; - setState(() { - columnWidths = List.generate(widget.titles.length, (index) { - if (index == 1) { - return newScreenWidth * - 0.12; // 20% of screen width for the second column - } else if (index == 9) { - return newScreenWidth * - 0.2; // 25% of screen width for the tenth column - } - return newScreenWidth * - 0.09; // Default to 10% of screen width for other columns - }); - }); - } - - @override - Widget build(BuildContext context) { - final screenWidth = MediaQuery.of(context).size.width; - - // Initialize column widths if they are still set to placeholder values - if (columnWidths.every((width) => width == 150.0)) { - columnWidths = List.generate(widget.titles.length, (index) { - if (index == 1) { - return screenWidth * - 0.12; // 20% of screen width for the second column - } else if (index == 9) { - return screenWidth * 0.2; // 25% of screen width for the tenth column - } - return screenWidth * - 0.09; // Default to 10% of screen width for other columns - }); - setState(() {}); - } - return SizedBox( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: FittedBox( - child: Column( - children: [ - // Header Row with Resizable Columns - Container( - color: ColorsManager.CircleRolesBackground, - child: Row( - children: List.generate(widget.titles.length, (index) { - return Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - FittedBox( - child: Container( - padding: const EdgeInsets.only(left: 10, right: 10), - width: columnWidths[index], - child: Text( - widget.titles[index], - style: const TextStyle( - fontWeight: FontWeight.w400, - fontSize: 13, - color: ColorsManager.grayColor, - ), - ), - ), - ), - GestureDetector( - onHorizontalDragUpdate: (details) { - setState(() { - columnWidths[index] = (columnWidths[index] + - details.delta.dx) - .clamp( - 150.0, 300.0); // Minimum & Maximum size - }); - }, - child: MouseRegion( - cursor: SystemMouseCursors - .resizeColumn, // Set the cursor to resize - child: Container( - color: Colors.green, - child: Container( - color: ColorsManager.boxDivider, - width: 1, - height: 50, // Height of the header cell - ), - ), - ), - ), - ], - ); - }), - ), - ), - // Data Rows with Dividers - Container( - color: ColorsManager.whiteColors, - child: Column( - children: widget.rows.map((row) { - int rowIndex = widget.rows.indexOf(row); - return Column( - children: [ - Container( - child: Padding( - padding: const EdgeInsets.only( - left: 5, top: 10, right: 5, bottom: 10), - child: Row( - children: List.generate(row.length, (index) { - return SizedBox( - width: columnWidths[index], - child: SizedBox( - child: Padding( - padding: const EdgeInsets.only( - left: 15, right: 10), - child: row[index], - ), - ), - ); - }), - ), - ), - ), - if (rowIndex < widget.rows.length - 1) - Row( - children: - List.generate(widget.titles.length, (index) { - return SizedBox( - width: columnWidths[index], - child: const Divider( - color: ColorsManager.boxDivider, - thickness: 1, - height: 1, - ), - ); - })) - // Add a Divider below each row except the last one - ], - ); - }).toList(), - ), - ), - ], - ), - ), - ), - ); - } -} - - - - // Widget build(BuildContext context) { - // return Scaffold( - // body: SingleChildScrollView( - // scrollDirection: Axis.horizontal, - // child: SingleChildScrollView( - // scrollDirection: Axis.vertical, - // child: Column( - // children: [ - // // Header Row with Resizable Columns - // Container( - // color: Colors.green, - // child: Row( - // children: List.generate(widget.titles.length, (index) { - // return Row( - // children: [ - // Container( - // width: columnWidths[index], - // decoration: const BoxDecoration( - // color: Colors.green, - // ), - // child: Text( - // widget.titles[index], - // style: TextStyle(fontWeight: FontWeight.bold), - // textAlign: TextAlign.center, - // ), - // ), - // GestureDetector( - // onHorizontalDragUpdate: (details) { - // setState(() { - // columnWidths[index] = (columnWidths[index] + - // details.delta.dx) - // .clamp(50.0, 300.0); // Minimum & Maximum size - // }); - // }, - // child: MouseRegion( - // cursor: SystemMouseCursors - // .resizeColumn, // Set the cursor to resize - // child: Container( - // color: Colors.green, - // child: Container( - // color: Colors.black, - // width: 1, - // height: 50, // Height of the header cell - // ), - // ), - // ), - // ), - // ], - // ); - // }), - // ), - // ), - // // Data Rows - // ...widget.rows.map((row) { - // return Row( - // children: List.generate(row.length, (index) { - // return Container( - // width: columnWidths[index], - // child: row[index], - // ); - // }), - // ); - // }).toList(), - // ], - // ), - // ), - // ), - // ); - // } \ No newline at end of file diff --git a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart index 05f924ca..da003536 100644 --- a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart +++ b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart @@ -2,11 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_state.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/view/users_page.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/users_page.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; @@ -17,8 +16,7 @@ class RolesAndPermissionPage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (BuildContext context) => - RolesPermissionBloc()..add(const GetRoles()), + create: (BuildContext context) => RolesPermissionBloc(), child: BlocConsumer( listener: (context, state) {}, builder: (context, state) { @@ -77,8 +75,8 @@ class RolesAndPermissionPage extends StatelessWidget { ), ], ), - scaffoldBody: BlocProvider( - create: (context) => UsersBloc()..add(const GetUsers()), + scaffoldBody: BlocProvider( + create: (context) => UserTableBloc()..add(const GetUsers()), child: const UsersPage(), ) // _blocRole.tapSelect == false diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 58dc0d9d..5d2464e6 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -252,7 +252,6 @@ class CommunitySpaceManagementApi { path: ApiEndpoints.getSpaceHierarchy .replaceAll('{communityId}', communityId), expectedResponseModel: (json) { - print('=-=-=-=$json'); final spaceModels = (json['data'] as List) .map((spaceJson) => SpaceModel.fromJson(spaceJson)) .toList(); diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index 22083191..d5cb8243 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -1,5 +1,7 @@ +import 'dart:convert'; +import 'package:dio/dio.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; @@ -33,7 +35,7 @@ class UserPermissionApi { return response ?? []; } - Future sendInviteUser({ + Future sendInviteUser({ String? firstName, String? lastName, String? email, @@ -42,22 +44,58 @@ class UserPermissionApi { String? roleUuid, List? spaceUuids, }) async { - final response = await _httpService.post( - path: ApiEndpoints.permission, - showServerMessage: true, - body: { + try { + final body = { "firstName": firstName, "lastName": lastName, "email": email, - "jobTitle": jobTitle, - "phoneNumber": phoneNumber, + "jobTitle": jobTitle != '' ? jobTitle : " ", + "phoneNumber": phoneNumber != '' ? phoneNumber : " ", "roleUuid": roleUuid, - "spaceUuids": spaceUuids - }, - expectedResponseModel: (json) { - print(json); - }, - ); - return response ?? []; + "projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c", + "spaceUuids": spaceUuids, + }; + final response = await _httpService.post( + path: ApiEndpoints.inviteUser, + showServerMessage: true, + body: jsonEncode(body), + expectedResponseModel: (json) { + if (json['statusCode'] != 400) { + return json["success"]; + } else { + return false; + } + }, + ); + + return response ?? []; + } on DioException catch (e) { + return false; + } catch (e) { + return false; + } + } + + Future checkEmail(email) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.checkEmail, + showServerMessage: true, + body: {"email": email}, + expectedResponseModel: (json) { + if (json['statusCode'] != 400) { + return json["message"]; + } + }, + ); + return response ?? []; + } on DioException catch (e) { + if (e.response != null) { + final errorMessage = e.response?.data['error']; + return errorMessage; + } + } catch (e) { + return e.toString(); + } } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 28923538..da746fbd 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -1,6 +1,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; abstract class ApiEndpoints { + static const String projectUuid = "0e62577c-06fa-41b9-8a92-99a21fbaf51c"; static String baseUrl = dotenv.env['BASE_URL'] ?? ''; static const String signUp = '/authentication/user/signup'; static const String login = '/authentication/user/login'; @@ -38,23 +39,32 @@ abstract class ApiEndpoints { static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}'; // Space Module - static const String createSpace = '/communities/{communityId}/spaces'; - static const String listSpaces = '/communities/{communityId}/spaces'; + static const String createSpace = + '/projects/${projectUuid}/communities/{communityId}/spaces'; + static const String listSpaces = + '/projects/${projectUuid}/communities/{communityId}/spaces'; static const String deleteSpace = - '/communities/{communityId}/spaces/{spaceId}'; + '/projects/${projectUuid}/communities/{communityId}/spaces/{spaceId}'; static const String updateSpace = - '/communities/{communityId}/spaces/{spaceId}'; - static const String getSpace = '/communities/{communityId}/spaces/{spaceId}'; - static const String getSpaceHierarchy = '/communities/{communityId}/spaces'; + '/projects/${projectUuid}/communities/{communityId}/spaces/{spaceId}'; + static const String getSpace = + '/projects/${projectUuid}/communities/{communityId}/spaces/{spaceId}'; + static const String getSpaceHierarchy = + '/projects/${projectUuid}/communities/{communityId}/spaces'; // Community Module - static const String createCommunity = '/communities'; - static const String getCommunityList = '/communities'; - static const String getCommunityById = '/communities/{communityId}'; - static const String updateCommunity = '/communities/{communityId}'; - static const String deleteCommunity = '/communities/{communityId}'; - static const String getUserCommunities = '/communities/user/{userUuid}'; - static const String createUserCommunity = '/communities/user'; + static const String createCommunity = '/projects/${projectUuid}/communities'; + static const String getCommunityList = '/projects/${projectUuid}/communities'; + static const String getCommunityById = + '/projects/${projectUuid}/communities/{communityId}'; + static const String updateCommunity = + '/projects/${projectUuid}/communities/{communityId}'; + static const String deleteCommunity = + '/projects/${projectUuid}/communities/{communityId}'; + static const String getUserCommunities = + '/projects/${projectUuid}/communities/user/{userUuid}'; + static const String createUserCommunity = + '/projects/${projectUuid}/communities/user'; static const String getDeviceLogsByDate = '/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}'; @@ -90,5 +100,7 @@ abstract class ApiEndpoints { static const String roleTypes = '/role/types'; static const String permission = '/permission/{roleUuid}'; static const String inviteUser = '/invite-user'; + static const String checkEmail = '/invite-user/check-email'; // static const String updateAutomation = '/automation/{automationId}'; + // https://syncrow-dev.azurewebsites.net/invite-user/check-email } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 8b271d5e..1f4074c4 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -394,5 +394,8 @@ class Assets { static const String arrowDown = 'assets/icons/arrow_down.svg'; static const String userManagement = 'assets/icons/user_management.svg'; + static const String filterTableIcon = 'assets/icons/filter_table_icon.svg'; + static const String ZtoAIcon = 'assets/icons/ztoa_icon.png'; + static const String AtoZIcon = 'assets/icons/atoz_icon.png'; } //user_management.svg From c834ad0670875d3d23b817498fbe0e8e89825614 Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 29 Dec 2024 09:52:13 +0300 Subject: [PATCH 021/175] add_new_user_dialog --- .../add_user_dialog/bloc/users_bloc.dart | 1 - .../view/permission_management.dart | 82 ++++++++++++++++--- 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index f0ddf01e..dbace8ad 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -199,7 +199,6 @@ class UsersBloc extends Bloc { node.title.toLowerCase().contains(searchTerm.toLowerCase()); bool childMatch = _searchRolePermission(node.subOptions, searchTerm); node.isHighlighted = isMatch || childMatch; - anyMatch = anyMatch || node.isHighlighted; } return anyMatch; diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart index b16cd8d2..266d431e 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart @@ -208,22 +208,52 @@ class _PermissionManagementState extends State { itemCount: subOption.subOptions.length, itemBuilder: (context, index) { final child = subOption.subOptions[index]; - return CheckboxListTile( - selectedTileColor: child.isHighlighted + return Container( + color: option.isHighlighted ? Colors.blue.shade50 : Colors.white, - dense: true, - controlAffinity: ListTileControlAffinity.leading, - title: Text( - child.title, - style: context.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w400, - fontSize: 12, - color: ColorsManager.lightGreyColor), + child: Row( + children: [ + Builder( + builder: (context) { + final checkState = + checkifOneOfthemChecked(PermissionOption( + id: child.id, + title: child.title, + subOptions: [child], + )); + + if (checkState == CheckState.all) { + return Image.asset( + Assets.CheckBoxChecked, + width: 20, + height: 20, + ); + } else if (checkState == CheckState.some) { + return Image.asset( + Assets.rectangleCheckBox, + width: 20, + height: 20, + ); + } else { + return Image.asset( + Assets.emptyBox, + width: 20, + height: 20, + ); + } + }, + ), + const SizedBox(width: 8), + Text( + child.title, + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.lightGreyColor), + ), + ], ), - value: child.isChecked, - onChanged: (value) => toggleOptionById(child.id), - enabled: false, ); }, ), @@ -237,3 +267,29 @@ class _PermissionManagementState extends State { ); } } + + + // Container( + // height: 50, + // width: 120, + // child: CheckboxListTile( + // activeColor: ColorsManager.dialogBlueTitle, + // selectedTileColor: child.isHighlighted + // ? Colors.blue.shade50 + // : Colors.white, + // dense: true, + // controlAffinity: + // ListTileControlAffinity.leading, + // title: Text( + // child.title, + // style: context.textTheme.bodyMedium?.copyWith( + // fontWeight: FontWeight.w400, + // fontSize: 12, + // color: ColorsManager.lightGreyColor), + // ), + // value: child.isChecked, + // onChanged: (value) => + // toggleOptionById(child.id), + // enabled: false, + // ), + // ), \ No newline at end of file From 8f7b46be251c160b6bf99914d23d7255ba60754e Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 29 Dec 2024 11:22:11 +0300 Subject: [PATCH 022/175] fixes checkEmail error --- lib/services/user_permission.dart | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index d5cb8243..91e45073 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -76,7 +76,9 @@ class UserPermissionApi { } } - Future checkEmail(email) async { + + + Future checkEmail(String email) async { try { final response = await _httpService.post( path: ApiEndpoints.checkEmail, @@ -84,16 +86,26 @@ class UserPermissionApi { body: {"email": email}, expectedResponseModel: (json) { if (json['statusCode'] != 400) { - return json["message"]; + var message = json["message"]; + if (message is String) { + return message; + } else { + return 'Unexpected message format'; + } } + return null; }, ); - return response ?? []; + return response ?? 'Unknown error occurred'; } on DioException catch (e) { if (e.response != null) { + print(e.response?.data['error']); final errorMessage = e.response?.data['error']; - return errorMessage; + return errorMessage is String + ? errorMessage + : 'Error occurred while checking email'; } + return 'Error occurred while checking email'; } catch (e) { return e.toString(); } From 9e5cbc285ebd5804382aa5134bbe1c80ea8fb674 Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 29 Dec 2024 11:22:45 +0300 Subject: [PATCH 023/175] checkEmail --- lib/services/user_permission.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index 91e45073..0e31795a 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -76,8 +76,6 @@ class UserPermissionApi { } } - - Future checkEmail(String email) async { try { final response = await _httpService.post( @@ -99,7 +97,6 @@ class UserPermissionApi { return response ?? 'Unknown error occurred'; } on DioException catch (e) { if (e.response != null) { - print(e.response?.data['error']); final errorMessage = e.response?.data['error']; return errorMessage is String ? errorMessage From edf8bdfdcd106c9efedd50f723391d10dff52cff Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 30 Dec 2024 11:59:37 +0400 Subject: [PATCH 024/175] added buttons in space management page --- .../view/spaces_management_page.dart | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart index 8cad58b2..fb893913 100644 --- a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart +++ b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart @@ -4,7 +4,6 @@ import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_vie 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/bloc/space_management_state.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'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart'; @@ -40,6 +39,28 @@ class SpaceManagementPageState extends State { appBarTitle: Text('Space Management', style: Theme.of(context).textTheme.headlineLarge), enableMenuSidebar: false, + centerBody: Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () {}, + child: Text( + 'Community Structure', + style: Theme.of(context).textTheme.bodyLarge, + ), + ), + const SizedBox(width: 20), + GestureDetector( + onTap: () {}, + child: Text( + 'Space Model', + style: Theme.of(context).textTheme.bodyLarge, + ), + ), + ], + ), + ), rightBody: const NavigateHomeGridView(), scaffoldBody: BlocBuilder( builder: (context, state) { From 79358642089a26a95f8cab5c0c77541e0db3ec0f Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 31 Dec 2024 10:40:32 +0400 Subject: [PATCH 025/175] fixed community structure --- .../bloc/space_management_bloc.dart | 40 ++++++++ .../bloc/space_management_event.dart | 3 + .../view/spaces_management_page.dart | 94 ++++++++----------- .../bloc/center_body_bloc.dart | 30 ++++++ .../view/center_body_widget.dart | 75 +++++++++++++++ 5 files changed, 185 insertions(+), 57 deletions(-) create mode 100644 lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart create mode 100644 lib/pages/spaces_management/structure_selector/view/center_body_widget.dart diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index ffa5687e..9d6af5a3 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -26,6 +26,7 @@ class SpaceManagementBloc on(_onFetchProducts); on(_onSelectSpace); on(_onNewCommunity); + on(_onBlankState); } void _onUpdateCommunity( @@ -103,6 +104,45 @@ class SpaceManagementBloc } } + Future _onBlankState( + BlankStateEvent event, Emitter emit) async { + try { + final previousState = state; + + if (previousState is SpaceManagementLoaded || + previousState is BlankState) { + final prevCommunities = (previousState as dynamic).communities ?? []; + emit(BlankState( + communities: List.from(prevCommunities), + products: _cachedProducts ?? [], + )); + return; + } + + final communities = await _api.fetchCommunities(); + final updatedCommunities = + await Future.wait(communities.map((community) async { + final spaces = await _fetchSpacesForCommunity(community.uuid); + return CommunityModel( + uuid: community.uuid, + createdAt: community.createdAt, + updatedAt: community.updatedAt, + name: community.name, + description: community.description, + spaces: spaces, + region: community.region, + ); + })); + + emit(BlankState( + communities: updatedCommunities, + products: _cachedProducts ?? [], + )); + } catch (error) { + emit(SpaceManagementError('Error loading communities: $error')); + } + } + void _onLoadCommunityAndSpaces( LoadCommunityAndSpacesEvent event, Emitter emit, diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart index 9e3dcc74..bf79d286 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart @@ -140,3 +140,6 @@ class LoadSpaceHierarchyEvent extends SpaceManagementEvent { @override List get props => [communityId]; } + + +class BlankStateEvent extends SpaceManagementEvent {} diff --git a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart index fb893913..960350a3 100644 --- a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart +++ b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart @@ -1,16 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; +import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_bloc.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/bloc/space_management_state.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'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/structure_selector/view/center_body_widget.dart'; import 'package:syncrow_web/services/product_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; + class SpaceManagementPage extends StatefulWidget { const SpaceManagementPage({super.key}); @@ -21,70 +22,49 @@ class SpaceManagementPage extends StatefulWidget { class SpaceManagementPageState extends State { final CommunitySpaceManagementApi _api = CommunitySpaceManagementApi(); final ProductApi _productApi = ProductApi(); - Map> communitySpaces = {}; - List products = []; - bool isProductDataLoaded = false; - - @override - void initState() { - super.initState(); - } @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => SpaceManagementBloc(_api, _productApi) - ..add(LoadCommunityAndSpacesEvent()), + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (_) => SpaceManagementBloc(_api, _productApi) + ..add(LoadCommunityAndSpacesEvent()), + ), + BlocProvider( + create: (_) => CenterBodyBloc(), + ), + ], child: WebScaffold( appBarTitle: Text('Space Management', style: Theme.of(context).textTheme.headlineLarge), enableMenuSidebar: false, - centerBody: Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () {}, - child: Text( - 'Community Structure', - style: Theme.of(context).textTheme.bodyLarge, - ), - ), - const SizedBox(width: 20), - GestureDetector( - onTap: () {}, - child: Text( - 'Space Model', - style: Theme.of(context).textTheme.bodyLarge, - ), - ), - ], - ), - ), + centerBody: CenterBodyWidget(), rightBody: const NavigateHomeGridView(), scaffoldBody: BlocBuilder( - builder: (context, state) { - if (state is SpaceManagementLoading) { - return const Center(child: CircularProgressIndicator()); - } else if (state is BlankState) { - return LoadedSpaceView( - communities: state.communities, - selectedCommunity: null, - selectedSpace: null, - products: state.products, - ); - } else if (state is SpaceManagementLoaded) { - return LoadedSpaceView( - communities: state.communities, - selectedCommunity: state.selectedCommunity, - selectedSpace: state.selectedSpace, - products: state.products, - ); - } else if (state is SpaceManagementError) { - return Center(child: Text('Error: ${state.errorMessage}')); - } - return Container(); - }), + builder: (context, state) { + if (state is SpaceManagementLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is BlankState) { + return LoadedSpaceView( + communities: state.communities, + selectedCommunity: null, + selectedSpace: null, + products: state.products, + ); + } else if (state is SpaceManagementLoaded) { + return LoadedSpaceView( + communities: state.communities, + selectedCommunity: state.selectedCommunity, + selectedSpace: state.selectedSpace, + products: state.products, + ); + } else if (state is SpaceManagementError) { + return Center(child: Text('Error: ${state.errorMessage}')); + } + return Container(); + }, + ), ), ); } diff --git a/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart b/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart new file mode 100644 index 00000000..4fe4f413 --- /dev/null +++ b/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart @@ -0,0 +1,30 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +// Define Events +abstract class CenterBodyEvent {} + +class Button1PressedEvent extends CenterBodyEvent {} + +class Button2PressedEvent extends CenterBodyEvent {} + +// Define States +abstract class CenterBodyState {} + +class InitialState extends CenterBodyState {} + +class Button1State extends CenterBodyState {} + +class Button2State extends CenterBodyState {} + +// Bloc Implementation +class CenterBodyBloc extends Bloc { + CenterBodyBloc() : super(InitialState()) { + on((event, emit) { + emit(Button1State()); + }); + + on((event, emit) { + emit(Button2State()); + }); + } +} diff --git a/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart b/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart new file mode 100644 index 00000000..ffacc29e --- /dev/null +++ b/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.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 '../bloc/center_body_bloc.dart'; + +class CenterBodyWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is InitialState) { + context.read().add(Button1PressedEvent()); + } + if (state is Button1State) { + context.read().add(BlankStateEvent()); + } + + return Container( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: () { + context.read().add(Button1PressedEvent()); + }, + child: Text( + 'Community Structure', + style: Theme.of(context).textTheme.bodyLarge!.copyWith( + fontWeight: state is Button1State + ? FontWeight.bold + : FontWeight.normal, + color: state is Button1State + ? Theme.of(context).textTheme.bodyLarge!.color + : Theme.of(context) + .textTheme + .bodyLarge! + .color! + .withOpacity(0.5), + ), + ), + ), + SizedBox(width: 20), + GestureDetector( + onTap: () { + context.read().add(Button2PressedEvent()); + }, + child: Text( + 'Space Model', + style: Theme.of(context).textTheme.bodyLarge!.copyWith( + fontWeight: state is Button2State + ? FontWeight.bold + : FontWeight.normal, + color: state is Button2State + ? Theme.of(context).textTheme.bodyLarge!.color + : Theme.of(context) + .textTheme + .bodyLarge! + .color! + .withOpacity(0.5), + ), + ), + ), + ], + ), + ], + ), + ); + }, + ); + } +} From fa16eaf82f0603194ce6ac57d85f5cad39b47e0b Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 31 Dec 2024 11:32:51 +0400 Subject: [PATCH 026/175] rename state --- .../bloc/center_body_bloc.dart | 16 ++++++++-------- .../view/center_body_widget.dart | 18 +++++++++--------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart b/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart index 4fe4f413..a3148af2 100644 --- a/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart +++ b/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart @@ -3,28 +3,28 @@ import 'package:flutter_bloc/flutter_bloc.dart'; // Define Events abstract class CenterBodyEvent {} -class Button1PressedEvent extends CenterBodyEvent {} +class CommunityStructureSelectedEvent extends CenterBodyEvent {} -class Button2PressedEvent extends CenterBodyEvent {} +class SpaceModelSelectedEvent extends CenterBodyEvent {} // Define States abstract class CenterBodyState {} class InitialState extends CenterBodyState {} -class Button1State extends CenterBodyState {} +class CommunityStructureState extends CenterBodyState {} -class Button2State extends CenterBodyState {} +class SpaceModelState extends CenterBodyState {} // Bloc Implementation class CenterBodyBloc extends Bloc { CenterBodyBloc() : super(InitialState()) { - on((event, emit) { - emit(Button1State()); + on((event, emit) { + emit(CommunityStructureState()); }); - on((event, emit) { - emit(Button2State()); + on((event, emit) { + emit(SpaceModelState()); }); } } diff --git a/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart b/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart index ffacc29e..3957e4de 100644 --- a/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart +++ b/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart @@ -10,9 +10,9 @@ class CenterBodyWidget extends StatelessWidget { return BlocBuilder( builder: (context, state) { if (state is InitialState) { - context.read().add(Button1PressedEvent()); + context.read().add(CommunityStructureSelectedEvent()); } - if (state is Button1State) { + if (state is CommunityStructureState) { context.read().add(BlankStateEvent()); } @@ -25,15 +25,15 @@ class CenterBodyWidget extends StatelessWidget { children: [ GestureDetector( onTap: () { - context.read().add(Button1PressedEvent()); + context.read().add(CommunityStructureSelectedEvent()); }, child: Text( 'Community Structure', style: Theme.of(context).textTheme.bodyLarge!.copyWith( - fontWeight: state is Button1State + fontWeight: state is CommunityStructureState ? FontWeight.bold : FontWeight.normal, - color: state is Button1State + color: state is CommunityStructureState ? Theme.of(context).textTheme.bodyLarge!.color : Theme.of(context) .textTheme @@ -43,18 +43,18 @@ class CenterBodyWidget extends StatelessWidget { ), ), ), - SizedBox(width: 20), + const SizedBox(width: 20), GestureDetector( onTap: () { - context.read().add(Button2PressedEvent()); + context.read().add(SpaceModelSelectedEvent()); }, child: Text( 'Space Model', style: Theme.of(context).textTheme.bodyLarge!.copyWith( - fontWeight: state is Button2State + fontWeight: state is SpaceModelState ? FontWeight.bold : FontWeight.normal, - color: state is Button2State + color: state is SpaceModelState ? Theme.of(context).textTheme.bodyLarge!.color : Theme.of(context) .textTheme From b264f6042fb1b2feaff785d9cb0ba5124727727f Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 31 Dec 2024 11:20:32 +0300 Subject: [PATCH 027/175] remove package and change color name --- .../view/roles_and_permission.dart | 4 +-- .../view/spaces_access_view.dart | 4 +-- .../users_table/view/user_table.dart | 2 +- .../roles_and_permission/view/role_card.dart | 2 +- lib/pages/routiens/widgets/dragable_card.dart | 2 +- lib/utils/color_manager.dart | 4 +-- pubspec.lock | 32 ------------------- pubspec.yaml | 1 - 8 files changed, 9 insertions(+), 42 deletions(-) diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart index b054a88c..0bc16a94 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart @@ -52,7 +52,7 @@ class RolesAndPermission extends StatelessWidget { flex: 2, child: Container( decoration: const BoxDecoration( - color: ColorsManager.CircleRolesBackground, + color: ColorsManager.circleRolesBackground, borderRadius: BorderRadius.only( topRight: Radius.circular(20), topLeft: Radius.circular(20)), @@ -107,7 +107,7 @@ class RolesAndPermission extends StatelessWidget { Expanded( flex: 7, child: Container( - color: ColorsManager.CircleRolesBackground, + color: ColorsManager.circleRolesBackground, padding: const EdgeInsets.all(8.0), child: Container( color: ColorsManager.whiteColors, diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart index f942d1d5..081fab40 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart @@ -51,7 +51,7 @@ class SpacesAccessView extends StatelessWidget { flex: 2, child: Container( decoration: const BoxDecoration( - color: ColorsManager.CircleRolesBackground, + color: ColorsManager.circleRolesBackground, borderRadius: BorderRadius.only( topRight: Radius.circular(20), topLeft: Radius.circular(20)), @@ -105,7 +105,7 @@ class SpacesAccessView extends StatelessWidget { Expanded( flex: 7, child: Container( - color: ColorsManager.CircleRolesBackground, + color: ColorsManager.circleRolesBackground, padding: const EdgeInsets.all(8.0), child: Container( color: ColorsManager.whiteColors, diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart index 926ac92a..f42c0c03 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart @@ -88,7 +88,7 @@ class _DynamicTableScreenState extends State // Header Row with Resizable Columns Container( decoration: containerDecoration.copyWith( - color: ColorsManager.CircleRolesBackground, + color: ColorsManager.circleRolesBackground, borderRadius: BorderRadius.only( topLeft: Radius.circular(15), topRight: Radius.circular(15))), diff --git a/lib/pages/roles_and_permission/view/role_card.dart b/lib/pages/roles_and_permission/view/role_card.dart index b3f59ee9..b08b14ea 100644 --- a/lib/pages/roles_and_permission/view/role_card.dart +++ b/lib/pages/roles_and_permission/view/role_card.dart @@ -32,7 +32,7 @@ class RoleCard extends StatelessWidget { backgroundColor: ColorsManager.neutralGray, radius: 65, child: CircleAvatar( - backgroundColor: ColorsManager.CircleRolesBackground, + backgroundColor: ColorsManager.circleRolesBackground, radius: 62, child: Icon( Icons.admin_panel_settings, diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index e26d3d12..dd41e1a2 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -86,7 +86,7 @@ class DraggableCard extends StatelessWidget { height: 50, width: 50, decoration: BoxDecoration( - color: ColorsManager.CircleImageBackground, + color: ColorsManager.circleImageBackground, borderRadius: BorderRadius.circular(90), border: Border.all( color: ColorsManager.graysColor, diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 5549b566..91bfe98c 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -54,8 +54,8 @@ abstract class ColorsManager { static const Color neutralGray = Color(0xFFE5E5E5); static const Color warningRed = Color(0xFFFF6465); static const Color borderColor = Color(0xFFE5E5E5); - static const Color CircleImageBackground = Color(0xFFF4F4F4); - static const Color CircleRolesBackground = Color(0xFFF8F8F8); + static const Color circleImageBackground = Color(0xFFF4F4F4); + static const Color circleRolesBackground = Color(0xFFF8F8F8); static const Color activeGreen = Color(0xFF99FF93); static const Color activeGreenText = Color(0xFF008905); static const Color disabledPink = Color(0xFFFF9395); diff --git a/pubspec.lock b/pubspec.lock index 98c62a94..86d132c3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -312,14 +312,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" - intl_phone_number_input: - dependency: "direct main" - description: - name: intl_phone_number_input - sha256: "1c4328713a9503ab26a1fdbb6b00b4cada68c18aac922b35bedbc72eff1297c3" - url: "https://pub.dev" - source: hosted - version: "0.7.4" js: dependency: transitive description: @@ -352,30 +344,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" - libphonenumber_platform_interface: - dependency: transitive - description: - name: libphonenumber_platform_interface - sha256: f801f6c65523f56504b83f0890e6dad584ab3a7507dca65fec0eed640afea40f - url: "https://pub.dev" - source: hosted - version: "0.4.2" - libphonenumber_plugin: - dependency: transitive - description: - name: libphonenumber_plugin - sha256: c615021d9816fbda2b2587881019ed595ecdf54d999652d7e4cce0e1f026368c - url: "https://pub.dev" - source: hosted - version: "0.3.3" - libphonenumber_web: - dependency: transitive - description: - name: libphonenumber_web - sha256: "8186f420dbe97c3132283e52819daff1e55d60d6db46f7ea5ac42f42a28cc2ef" - url: "https://pub.dev" - source: hosted - version: "0.3.2" lints: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c99481d1..6951987c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,7 +53,6 @@ dependencies: uuid: ^4.4.2 time_picker_spinner: ^1.0.0 intl_phone_field: ^3.2.0 - intl_phone_number_input: ^0.7.4 dev_dependencies: flutter_test: From 65ad9c5edf4ea37aab10ebf679f0700155947310 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 2 Jan 2025 17:05:45 +0400 Subject: [PATCH 028/175] space model view --- .../bloc/space_management_bloc.dart | 39 ++++- .../bloc/space_management_event.dart | 2 + .../bloc/space_management_state.dart | 20 +++ .../all_spaces/model/product_model.dart | 1 - .../view/spaces_management_page.dart | 11 +- .../widgets/loaded_space_widget.dart | 26 +++- .../models/space_template_model.dart | 133 ++++++++++++++++++ .../space_model/view/space_model_page.dart | 81 +++++++++++ .../widgets/space_model_card_widget.dart | 123 ++++++++++++++++ .../widgets/subspace_chip_widget.dart | 34 +++++ .../view/center_body_widget.dart | 4 + lib/services/space_model_mang_api.dart | 37 +++++ lib/utils/color_manager.dart | 2 + lib/utils/constants/api_const.dart | 62 +++++--- 14 files changed, 542 insertions(+), 33 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/models/space_template_model.dart create mode 100644 lib/pages/spaces_management/space_model/view/space_model_page.dart create mode 100644 lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart create mode 100644 lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart create mode 100644 lib/services/space_model_mang_api.dart diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 9d6af5a3..15b014d1 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -4,17 +4,20 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_mod import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/services/product_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; +import 'package:syncrow_web/services/space_model_mang_api.dart'; class SpaceManagementBloc extends Bloc { final CommunitySpaceManagementApi _api; final ProductApi _productApi; + final SpaceModelManagementApi _spaceModelApi; List? _cachedProducts; - SpaceManagementBloc(this._api, this._productApi) + SpaceManagementBloc(this._api, this._productApi, this._spaceModelApi) : super(SpaceManagementInitial()) { on(_onLoadCommunityAndSpaces); on(_onUpdateSpacePosition); @@ -27,6 +30,7 @@ class SpaceManagementBloc on(_onSelectSpace); on(_onNewCommunity); on(_onBlankState); + on(_onLoadSpaceModel); } void _onUpdateCommunity( @@ -410,4 +414,37 @@ class SpaceManagementBloc } return result.toList(); // Convert back to a list } + + void _onLoadSpaceModel( + SpaceModelLoadEvent event, Emitter emit) async { + emit(SpaceManagementLoading()); + try { + List communities = await _api.fetchCommunities(); + + List updatedCommunities = await Future.wait( + communities.map((community) async { + List spaces = + await _fetchSpacesForCommunity(community.uuid); + return CommunityModel( + uuid: community.uuid, + createdAt: community.createdAt, + updatedAt: community.updatedAt, + name: community.name, + description: community.description, + spaces: spaces, // New spaces list + region: community.region, + ); + }).toList(), + ); + + List spaceModels = + await _spaceModelApi.listSpaceModels(page: 1); + emit(SpaceModelLoaded( + communities: updatedCommunities, + products: _cachedProducts ?? [], + spaceModels: spaceModels)); + } catch (e) { + emit(SpaceManagementError('Error loading communities and spaces: $e')); + } + } } diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart index bf79d286..d25534b4 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_event.dart @@ -143,3 +143,5 @@ class LoadSpaceHierarchyEvent extends SpaceManagementEvent { class BlankStateEvent extends SpaceManagementEvent {} + +class SpaceModelLoadEvent extends SpaceManagementEvent {} diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart index eca8c16f..3ceafba9 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart @@ -2,6 +2,7 @@ import 'package:equatable/equatable.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'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; abstract class SpaceManagementState extends Equatable { const SpaceManagementState(); @@ -27,6 +28,10 @@ class SpaceManagementLoaded extends SpaceManagementState { this.selectedSpace}); } +class SpaceModelManagenetLoaded extends SpaceManagementState { + SpaceModelManagenetLoaded(); +} + class BlankState extends SpaceManagementState { final List communities; final List products; @@ -54,3 +59,18 @@ class SpaceManagementError extends SpaceManagementState { @override List get props => [errorMessage]; } + +class SpaceModelLoaded extends SpaceManagementState { + final List spaceModels; + final List products; + final List communities; + + SpaceModelLoaded({ + required this.communities, + required this.products, + required this.spaceModels, + }); + + @override + List get props => [communities, products, spaceModels]; +} diff --git a/lib/pages/spaces_management/all_spaces/model/product_model.dart b/lib/pages/spaces_management/all_spaces/model/product_model.dart index 5a0e92e1..557c106b 100644 --- a/lib/pages/spaces_management/all_spaces/model/product_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/product_model.dart @@ -19,7 +19,6 @@ class ProductModel { // Factory method to create a Product from JSON factory ProductModel.fromMap(Map json) { - String icon = _mapIconToProduct(json['prodType']); return ProductModel( uuid: json['uuid'], catName: json['catName'], diff --git a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart index 960350a3..33edceb2 100644 --- a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart +++ b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart @@ -9,9 +9,9 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/loaded_sp import 'package:syncrow_web/pages/spaces_management/structure_selector/view/center_body_widget.dart'; import 'package:syncrow_web/services/product_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; +import 'package:syncrow_web/services/space_model_mang_api.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; - class SpaceManagementPage extends StatefulWidget { const SpaceManagementPage({super.key}); @@ -22,13 +22,13 @@ class SpaceManagementPage extends StatefulWidget { class SpaceManagementPageState extends State { final CommunitySpaceManagementApi _api = CommunitySpaceManagementApi(); final ProductApi _productApi = ProductApi(); - + final SpaceModelManagementApi _spaceModelApi = SpaceModelManagementApi(); @override Widget build(BuildContext context) { return MultiBlocProvider( providers: [ BlocProvider( - create: (_) => SpaceManagementBloc(_api, _productApi) + create: (_) => SpaceManagementBloc(_api, _productApi, _spaceModelApi) ..add(LoadCommunityAndSpacesEvent()), ), BlocProvider( @@ -59,7 +59,10 @@ class SpaceManagementPageState extends State { selectedSpace: state.selectedSpace, products: state.products, ); - } else if (state is SpaceManagementError) { + }else if(state is SpaceModelLoaded){ + return LoadedSpaceView(communities: state.communities, products: state.products, spaceModels: state.spaceModels); + } + else if (state is SpaceManagementError) { return Center(child: Text('Error: ${state.errorMessage}')); } return Container(); 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 7ce56914..6bfd3ee3 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 @@ -5,12 +5,15 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/gradient_canvas_border_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/view/space_model_page.dart'; class LoadedSpaceView extends StatefulWidget { final List communities; final CommunityModel? selectedCommunity; final SpaceModel? selectedSpace; final List? products; + final List? spaceModels; const LoadedSpaceView({ super.key, @@ -18,6 +21,7 @@ class LoadedSpaceView extends StatefulWidget { this.selectedCommunity, this.selectedSpace, this.products, + this.spaceModels, }); @override @@ -27,6 +31,9 @@ class LoadedSpaceView extends StatefulWidget { class _LoadedStateViewState extends State { @override Widget build(BuildContext context) { + final bool hasSpaceModels = + widget.spaceModels != null && widget.spaceModels!.isNotEmpty; + return Stack( clipBehavior: Clip.none, children: [ @@ -38,13 +45,18 @@ class _LoadedStateViewState extends State { widget.selectedCommunity?.uuid ?? '', ), - CommunityStructureArea( - selectedCommunity: widget.selectedCommunity, - selectedSpace: widget.selectedSpace, - spaces: widget.selectedCommunity?.spaces ?? [], - products: widget.products, - communities: widget.communities, - ), + hasSpaceModels + ? Expanded( + child: SpaceModelPage( + spaceModels: widget.spaceModels ??[], + )) + : CommunityStructureArea( + selectedCommunity: widget.selectedCommunity, + selectedSpace: widget.selectedSpace, + spaces: widget.selectedCommunity?.spaces ?? [], + products: widget.products, + communities: widget.communities, + ), ], ), const GradientCanvasBorderWidget(), 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 new file mode 100644 index 00000000..3d29c29f --- /dev/null +++ b/lib/pages/spaces_management/space_model/models/space_template_model.dart @@ -0,0 +1,133 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; + +class SpaceTemplateModel { + final String uuid; + final DateTime createdAt; + final DateTime updatedAt; + final String modelName; + final bool disabled; + final List subspaceModels; + final List tags; + + SpaceTemplateModel({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.modelName, + required this.disabled, + required this.subspaceModels, + required this.tags, + }); + + factory SpaceTemplateModel.fromJson(Map json) { + return SpaceTemplateModel( + uuid: json['uuid'] ?? '', + createdAt: DateTime.parse(json['createdAt']), + updatedAt: DateTime.parse(json['updatedAt']), + modelName: json['modelName'] ?? '', + disabled: json['disabled'] ?? false, + subspaceModels: (json['subspaceModels'] as List) + .map((item) => SubspaceModel.fromJson(item)) + .toList(), + tags: (json['tags'] as List) + .map((item) => TagModel.fromJson(item)) + .toList(), + ); + } + + Map toJson() { + return { + 'uuid': uuid, + 'createdAt': createdAt.toIso8601String(), + 'updatedAt': updatedAt.toIso8601String(), + 'modelName': modelName, + 'disabled': disabled, + 'subspaceModels': subspaceModels.map((e) => e.toJson()).toList(), + 'tags': tags.map((e) => e.toJson()).toList(), + }; + } +} + +class SubspaceModel { + final String uuid; + final DateTime createdAt; + final DateTime updatedAt; + final String subspaceName; + final bool disabled; + final List tags; + + SubspaceModel({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.subspaceName, + required this.disabled, + required this.tags, + }); + + factory SubspaceModel.fromJson(Map json) { + return SubspaceModel( + uuid: json['uuid'] ?? '', + createdAt: DateTime.parse(json['createdAt']), + updatedAt: DateTime.parse(json['updatedAt']), + subspaceName: json['subspaceName'] ?? '', + disabled: json['disabled'] ?? false, + tags: (json['tags'] as List) + .map((item) => TagModel.fromJson(item)) + .toList(), + ); + } + + Map toJson() { + return { + 'uuid': uuid, + 'createdAt': createdAt.toIso8601String(), + 'updatedAt': updatedAt.toIso8601String(), + 'subspaceName': subspaceName, + 'disabled': disabled, + 'tags': tags.map((e) => e.toJson()).toList(), + }; + } +} + +class TagModel { + final String uuid; + final DateTime createdAt; + final DateTime updatedAt; + final String tag; + final bool disabled; + final ProductModel? product; + + TagModel({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.tag, + required this.disabled, + this.product, + }); + + factory TagModel.fromJson(Map json) { + return TagModel( + uuid: json['uuid'] ?? '', + createdAt: DateTime.parse(json['createdAt']), + updatedAt: DateTime.parse(json['updatedAt']), + tag: json['tag'] ?? '', + disabled: json['disabled'] ?? false, + product: json['product'] != null + ? ProductModel.fromMap(json['product']) + : null, + ); + } + + Map toJson() { + return { + 'uuid': uuid, + 'createdAt': createdAt.toIso8601String(), + 'updatedAt': updatedAt.toIso8601String(), + 'tag': tag, + 'disabled': disabled, + 'product': product?.toMap(), + }; + } +} diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart new file mode 100644 index 00000000..736fa2b5 --- /dev/null +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.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'; + +class SpaceModelPage extends StatelessWidget { + final List spaceModels; + + const SpaceModelPage({Key? key, required this.spaceModels}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: ColorsManager.whiteColors, + body: Padding( + padding: const EdgeInsets.fromLTRB(20.0, 16.0, 16.0, 16.0), + child: GridView.builder( + //clipBehavior: Clip.none, + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 14.0, + mainAxisSpacing: 14.0, + childAspectRatio: 3, + ), + itemCount: spaceModels.length + 1, + itemBuilder: (context, index) { + if (index == spaceModels.length) { + return GestureDetector( + onTap: () {}, + child: Container( + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(20), + boxShadow: const [ + BoxShadow( + color: ColorsManager.semiTransparentBlackColor, + blurRadius: 15, + offset: Offset(0, 4), + spreadRadius: 0, + ), + BoxShadow( + color: ColorsManager.semiTransparentBlackColor, + blurRadius: 25, + offset: Offset(0, 15), + spreadRadius: -5, + ), + ], + ), + child: Center( + child: Container( + width: 60, + height: 60, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.neutralGray, + border: Border.all( + color: ColorsManager.textFieldGreyColor, + width: 2.0, + ), + ), + child: const Icon( + Icons.add, + size: 40, + color: ColorsManager.spaceColor, // Icon color + ), + ), + ), + ), + ); + } + + final model = spaceModels[index]; + return SpaceModelCardWidget(model: model); + }, + ), + ), + ); + } +} 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 new file mode 100644 index 00000000..49128a3f --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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/subspace_chip_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class SpaceModelCardWidget extends StatelessWidget { + final SpaceTemplateModel model; + + const SpaceModelCardWidget({Key? key, required this.model}) : super(key: key); + + @override + Widget build(BuildContext context) { + final Map productTagCount = {}; + + for (var tag in model.tags) { + final prodIcon = tag.product?.icon ?? 'Unknown'; + productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + } + + for (var subspace in model.subspaceModels) { + for (var tag in subspace.tags) { + final prodIcon = tag.product?.icon ?? 'Unknown'; + productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + } + } + + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: const Offset(0, 3), + ), + ], + ), + padding: const EdgeInsets.fromLTRB(16.0, 14.0, 8.0, 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + child: Text( + model.modelName, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: ColorsManager.blackColor, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(height: 10), + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Wrap( + spacing: 3.0, + runSpacing: 3.0, + children: [ + for (var subspace in model.subspaceModels.take(3)) + SubspaceChipWidget(subspace: subspace.subspaceName), + ], + ), + ), + if (productTagCount.isNotEmpty) + Container( + width: 1, + height: double.infinity, + color: ColorsManager.softGray, + margin: const EdgeInsets.symmetric(horizontal: 4.0), + ), + const SizedBox(width: 7), + Expanded( + child: Wrap( + spacing: 4.0, + runSpacing: 4.0, + children: productTagCount.entries.map((entry) { + final prodType = entry.key; + final count = entry.value; + + return Chip( + label: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + prodType, + width: 15, + height: 16, + ), + const SizedBox(width: 4), + Text( + 'x$count', // Product count + style: const TextStyle(fontSize: 12), + ), + ], + ), + backgroundColor: ColorsManager.textFieldGreyColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: const BorderSide( + color: ColorsManager.transparentColor, + width: 0, + ), + ), + ); + }).toList(), + ), + ), + ], + ), + ), + const SizedBox(height: 5), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart new file mode 100644 index 00000000..debe3801 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class SubspaceChipWidget extends StatelessWidget { + final String subspace; + + const SubspaceChipWidget({ + Key? key, + required this.subspace, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Chip( + label: Text( + subspace, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + backgroundColor: ColorsManager.textFieldGreyColor, + labelStyle: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.spaceColor), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: const BorderSide( + color: Colors.transparent, + width: 0, + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart b/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart index 3957e4de..68598081 100644 --- a/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart +++ b/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart @@ -16,6 +16,10 @@ class CenterBodyWidget extends StatelessWidget { context.read().add(BlankStateEvent()); } + if (state is SpaceModelState) { + context.read().add(SpaceModelLoadEvent()); + } + return Container( child: Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart new file mode 100644 index 00000000..762f9178 --- /dev/null +++ b/lib/services/space_model_mang_api.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +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({int page = 1}) async { + try { + List spaceModels = []; + bool hasNext = true; + while (hasNext) { + await HTTPService().get( + path: ApiEndpoints.listSpaceModels + .replaceAll('{projectId}', TempConst.projectId), + queryParameters: {'page': page}, + expectedResponseModel: (json) { + List jsonData = json['data']; + hasNext = json['hasNext'] ?? false; + int currentPage = json['page'] ?? 1; + List spaceModelList = jsonData.map((jsonItem) { + return SpaceTemplateModel.fromJson(jsonItem); + }).toList(); + + spaceModels.addAll(spaceModelList); + page = currentPage + 1; + return spaceModelList; + }, + ); + } + return spaceModels; + } catch (e) { + debugPrint('Error fetching space models: $e'); + return []; + } + } +} diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 95d0f214..0d49eb24 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -54,5 +54,7 @@ abstract class ColorsManager { static const Color warningRed = Color(0xFFFF6465); static const Color borderColor = Color(0xFFE5E5E5); static const Color CircleImageBackground = Color(0xFFF4F4F4); + static const Color softGray = Color(0xFFD5D5D5); + static const Color semiTransparentBlack = Color(0x19000000) } //background: #background: #5D5D5D; diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index b8d23259..2fdca23a 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -11,12 +11,14 @@ abstract class ApiEndpoints { static const String visitorPassword = '/visitor-password'; static const String getDevices = '/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'; @@ -36,30 +38,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 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 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'; // 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'; @@ -68,13 +85,18 @@ abstract class ApiEndpoints { static const String getIconScene = '/scene/icon'; static const String createScene = '/scene/tap-to-run'; 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 getUnitScenes = + '/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/scenes'; + 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}'; static const String deleteAutomation = '/automation/{automationId}'; - static const String updateScene = '/scene/tap-to-run/{sceneId}'; + static const String updateScene = '/scene/tap-to-run/{sceneId}'; static const String updateAutomation = '/automation/{automationId}'; + + //space model + static const String listSpaceModels = '/projects/{projectId}/space-models'; } From 80dea5c12db3dbcbbd8a075ff89be09040c6a045 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 2 Jan 2025 17:55:04 +0400 Subject: [PATCH 029/175] fixed block flow --- .../all_spaces/bloc/space_management_bloc.dart | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 15b014d1..2434a32c 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -239,11 +239,15 @@ class SpaceManagementBloc SelectCommunityEvent event, Emitter emit, ) async { - _handleCommunitySpaceStateUpdate( - emit: emit, - selectedCommunity: event.selectedCommunity, - selectedSpace: null, - ); + try { + _handleCommunitySpaceStateUpdate( + emit: emit, + selectedCommunity: event.selectedCommunity, + selectedSpace: null, + ); + } catch (e) { + emit(SpaceManagementError('Error updating state: $e')); + } } void _onSelectSpace( @@ -267,7 +271,8 @@ class SpaceManagementBloc try { if (previousState is SpaceManagementLoaded || - previousState is BlankState) { + previousState is BlankState || + previousState is SpaceModelLoaded) { final communities = List.from( (previousState as dynamic).communities, ); From e12252db969e454887ac829b55c86a2807371b6e Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 3 Jan 2025 08:45:34 +0400 Subject: [PATCH 030/175] fixed navigation in between --- .../all_spaces/widgets/sidebar_widget.dart | 4 ++++ .../bloc/center_body_bloc.dart | 22 +++++-------------- .../bloc/center_body_event.dart | 8 +++++++ .../bloc/center_body_state.dart | 9 ++++++++ .../view/center_body_widget.dart | 6 +++-- lib/utils/color_manager.dart | 2 +- 6 files changed, 32 insertions(+), 19 deletions(-) create mode 100644 lib/pages/spaces_management/structure_selector/bloc/center_body_event.dart create mode 100644 lib/pages/spaces_management/structure_selector/bloc/center_body_state.dart 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 a58d73e9..2d557e25 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -8,6 +8,8 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_m import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_tile.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -186,6 +188,8 @@ class _SidebarWidgetState extends State { _selectedSpaceUuid = null; // Update the selected community }); + context.read().add(CommunitySelectedEvent()); + context.read().add( SelectCommunityEvent(selectedCommunity: community), ); diff --git a/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart b/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart index a3148af2..1de2ae13 100644 --- a/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart +++ b/lib/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart @@ -1,20 +1,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; - -// Define Events -abstract class CenterBodyEvent {} - -class CommunityStructureSelectedEvent extends CenterBodyEvent {} - -class SpaceModelSelectedEvent extends CenterBodyEvent {} - -// Define States -abstract class CenterBodyState {} - -class InitialState extends CenterBodyState {} - -class CommunityStructureState extends CenterBodyState {} - -class SpaceModelState extends CenterBodyState {} +import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart'; +import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_state.dart'; // Bloc Implementation class CenterBodyBloc extends Bloc { @@ -26,5 +12,9 @@ class CenterBodyBloc extends Bloc { on((event, emit) { emit(SpaceModelState()); }); + + on((event, emit) { + emit(CommunitySelectedState()); + }); } } diff --git a/lib/pages/spaces_management/structure_selector/bloc/center_body_event.dart b/lib/pages/spaces_management/structure_selector/bloc/center_body_event.dart new file mode 100644 index 00000000..72cdbd1c --- /dev/null +++ b/lib/pages/spaces_management/structure_selector/bloc/center_body_event.dart @@ -0,0 +1,8 @@ +// Define Events +abstract class CenterBodyEvent {} + +class CommunityStructureSelectedEvent extends CenterBodyEvent {} + +class SpaceModelSelectedEvent extends CenterBodyEvent {} + +class CommunitySelectedEvent extends CenterBodyEvent {} \ No newline at end of file diff --git a/lib/pages/spaces_management/structure_selector/bloc/center_body_state.dart b/lib/pages/spaces_management/structure_selector/bloc/center_body_state.dart new file mode 100644 index 00000000..73428dc5 --- /dev/null +++ b/lib/pages/spaces_management/structure_selector/bloc/center_body_state.dart @@ -0,0 +1,9 @@ +abstract class CenterBodyState {} + +class InitialState extends CenterBodyState {} + +class CommunityStructureState extends CenterBodyState {} + +class SpaceModelState extends CenterBodyState {} + +class CommunitySelectedState extends CenterBodyState {} diff --git a/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart b/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart index 68598081..45a6aaf7 100644 --- a/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart +++ b/lib/pages/spaces_management/structure_selector/view/center_body_widget.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.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/structure_selector/bloc/center_body_event.dart'; +import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_state.dart'; import '../bloc/center_body_bloc.dart'; class CenterBodyWidget extends StatelessWidget { @@ -34,10 +36,10 @@ class CenterBodyWidget extends StatelessWidget { child: Text( 'Community Structure', style: Theme.of(context).textTheme.bodyLarge!.copyWith( - fontWeight: state is CommunityStructureState + fontWeight: state is CommunityStructureState || state is CommunitySelectedState ? FontWeight.bold : FontWeight.normal, - color: state is CommunityStructureState + color: state is CommunityStructureState || state is CommunitySelectedState ? Theme.of(context).textTheme.bodyLarge!.color : Theme.of(context) .textTheme diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 0d49eb24..8d0aae43 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -55,6 +55,6 @@ abstract class ColorsManager { static const Color borderColor = Color(0xFFE5E5E5); static const Color CircleImageBackground = Color(0xFFF4F4F4); static const Color softGray = Color(0xFFD5D5D5); - static const Color semiTransparentBlack = Color(0x19000000) + static const Color semiTransparentBlack = Color(0x19000000); } //background: #background: #5D5D5D; From e0ff139f3091170d5c4512ab274b4a406f9104b6 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 3 Jan 2025 10:13:44 +0400 Subject: [PATCH 031/175] add create space model widget UI --- .../space_model/view/space_model_page.dart | 16 +- .../dialog/create_space_model_dialog.dart | 148 ++++++++++++++++++ 2 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 736fa2b5..432713db 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.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/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -20,15 +21,22 @@ class SpaceModelPage extends StatelessWidget { physics: const AlwaysScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, - crossAxisSpacing: 14.0, - mainAxisSpacing: 14.0, - childAspectRatio: 3, + crossAxisSpacing: 13.0, + mainAxisSpacing: 13.0, + childAspectRatio: 3.5, ), itemCount: spaceModels.length + 1, itemBuilder: (context, index) { if (index == spaceModels.length) { return GestureDetector( - onTap: () {}, + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return const CreateSpaceModelDialog(); + }, + ); + }, child: Container( decoration: BoxDecoration( color: ColorsManager.whiteColors, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart new file mode 100644 index 00000000..c253da02 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -0,0 +1,148 @@ +import 'package:flutter/material.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/utils/color_manager.dart'; + +class CreateSpaceModelDialog extends StatelessWidget { + const CreateSpaceModelDialog({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + return AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + backgroundColor: ColorsManager.whiteColors, + content: SizedBox( + width: screenWidth * 0.3, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Create New Space Model', + style: Theme.of(context) + .textTheme + .headlineLarge + ?.copyWith(color: ColorsManager.blackColor), + ), + const SizedBox(height: 16), + SizedBox( + width: screenWidth * 0.25, + child: TextField( + style: const TextStyle(color: ColorsManager.blackColor), + decoration: InputDecoration( + filled: true, + fillColor: ColorsManager.textFieldGreyColor, + hintText: 'Please enter the name', + hintStyle: + const TextStyle(color: ColorsManager.lightGrayColor), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.symmetric( + vertical: 12.0, + horizontal: 16.0, + ), + ), + ), + ), + const SizedBox(height: 16), + SizedBox( + width: screenWidth * 0.25, + child: Container( + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + border: Border.all( + color: ColorsManager.neutralGray, + width: 3.0, + ), + borderRadius: BorderRadius.circular(20), + ), + child: const Padding( + padding: + EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), + child: Row( + children: [ + Icon( + Icons.add, + color: ColorsManager.spaceColor, + ), + SizedBox(width: 10), + Expanded( + child: Text( + 'Create sub space', + style: TextStyle( + color: ColorsManager.blackColor, + fontSize: 16, + ), + ), + ), + ], + ), + ), + ), + ), + const SizedBox(height: 10), + SizedBox( + width: screenWidth * 0.25, + child: Container( + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + border: + Border.all(color: ColorsManager.neutralGray, width: 3.0), + borderRadius: BorderRadius.circular(20), + ), + child: const Padding( + padding: + EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), + child: Row( + children: [ + Icon( + Icons.add, + color: ColorsManager.spaceColor, + ), + SizedBox(width: 10), + Expanded( + child: Text( + 'Add devices', + style: TextStyle( + color: ColorsManager.blackColor, + fontSize: 16, + ), + ), + ), + ], + ), + ), + ), + ), + const SizedBox(height: 20), + SizedBox( + width: screenWidth * 0.25, + child: Row( + children: [ + Expanded( + child: CancelButton( + label: 'Cancel', + onPressed: () => Navigator.of(context).pop(), + )), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + onPressed: () {}, + backgroundColor: ColorsManager.secondaryColor, + borderRadius: 10, + foregroundColor: ColorsManager.whiteColors, + child: const Text('OK'), + ), + ), + ], + ), + ) + ], + ), + ), + ); + } +} From 944b981ee03f73b827ae12b43c6458f701a59000 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 3 Jan 2025 14:28:45 +0400 Subject: [PATCH 032/175] added subspace model events --- .../view/spaces_management_page.dart | 10 +- .../space_model/bloc/subspace_model_bloc.dart | 38 ++++ .../models/space_template_model.dart | 38 ++-- .../dialog/create_space_model_dialog.dart | 79 +++++--- .../dialog/create_subspace_model_dialog.dart | 188 ++++++++++++++++++ .../widgets/space_model_card_widget.dart | 8 +- lib/utils/constants/temp_const.dart | 2 +- 7 files changed, 307 insertions(+), 56 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart create mode 100644 lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart diff --git a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart index 33edceb2..77f878b7 100644 --- a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart +++ b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart @@ -59,10 +59,12 @@ class SpaceManagementPageState extends State { selectedSpace: state.selectedSpace, products: state.products, ); - }else if(state is SpaceModelLoaded){ - return LoadedSpaceView(communities: state.communities, products: state.products, spaceModels: state.spaceModels); - } - else if (state is SpaceManagementError) { + } else if (state is SpaceModelLoaded) { + return LoadedSpaceView( + communities: state.communities, + products: state.products, + spaceModels: state.spaceModels); + } else if (state is SpaceManagementError) { return Center(child: Text('Error: ${state.errorMessage}')); } return Container(); diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart new file mode 100644 index 00000000..423a5ca5 --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart @@ -0,0 +1,38 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +// Events +abstract class SubSpaceModelEvent {} + +class AddSubSpaceModel extends SubSpaceModelEvent { + final SubspaceTemplateModel subSpace; + AddSubSpaceModel(this.subSpace); +} + +class RemoveSubSpaceModel extends SubSpaceModelEvent { + final SubspaceTemplateModel subSpace; + RemoveSubSpaceModel(this.subSpace); +} + +// State +class SubSpaceModelState { + final List subSpaces; + SubSpaceModelState(this.subSpaces); +} + +// BLoC +class SubSpaceModelBloc extends Bloc { + SubSpaceModelBloc() : super(SubSpaceModelState([])) { + on((event, emit) { + final updatedSubSpaces = List.from(state.subSpaces) + ..add(event.subSpace); + emit(SubSpaceModelState(updatedSubSpaces)); + }); + + on((event, emit) { + final updatedSubSpaces = List.from(state.subSpaces) + ..remove(event.subSpace); + emit(SubSpaceModelState(updatedSubSpaces)); + }); + } +} 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 3d29c29f..2107b87f 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 @@ -1,4 +1,5 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:uuid/uuid.dart'; class SpaceTemplateModel { final String uuid; @@ -6,28 +7,33 @@ class SpaceTemplateModel { final DateTime updatedAt; final String modelName; final bool disabled; - final List subspaceModels; + final List subspaceModels; final List tags; + String internalId; SpaceTemplateModel({ required this.uuid, + String? internalId, required this.createdAt, required this.updatedAt, required this.modelName, required this.disabled, required this.subspaceModels, required this.tags, - }); + }) : internalId = internalId ?? const Uuid().v4(); factory SpaceTemplateModel.fromJson(Map json) { + final String internalId = json['internalId'] ?? const Uuid().v4(); + return SpaceTemplateModel( uuid: json['uuid'] ?? '', + internalId: internalId, createdAt: DateTime.parse(json['createdAt']), updatedAt: DateTime.parse(json['updatedAt']), modelName: json['modelName'] ?? '', disabled: json['disabled'] ?? false, subspaceModels: (json['subspaceModels'] as List) - .map((item) => SubspaceModel.fromJson(item)) + .map((item) => SubspaceTemplateModel.fromJson(item)) .toList(), tags: (json['tags'] as List) .map((item) => TagModel.fromJson(item)) @@ -48,28 +54,22 @@ class SpaceTemplateModel { } } -class SubspaceModel { - final String uuid; - final DateTime createdAt; - final DateTime updatedAt; +class SubspaceTemplateModel { + final String? uuid; final String subspaceName; final bool disabled; - final List tags; + final List? tags; - SubspaceModel({ - required this.uuid, - required this.createdAt, - required this.updatedAt, + SubspaceTemplateModel({ + this.uuid, required this.subspaceName, required this.disabled, - required this.tags, + this.tags, }); - factory SubspaceModel.fromJson(Map json) { - return SubspaceModel( + factory SubspaceTemplateModel.fromJson(Map json) { + return SubspaceTemplateModel( uuid: json['uuid'] ?? '', - createdAt: DateTime.parse(json['createdAt']), - updatedAt: DateTime.parse(json['updatedAt']), subspaceName: json['subspaceName'] ?? '', disabled: json['disabled'] ?? false, tags: (json['tags'] as List) @@ -81,11 +81,9 @@ class SubspaceModel { Map toJson() { return { 'uuid': uuid, - 'createdAt': createdAt.toIso8601String(), - 'updatedAt': updatedAt.toIso8601String(), 'subspaceName': subspaceName, 'disabled': disabled, - 'tags': tags.map((e) => e.toJson()).toList(), + 'tags': tags?.map((e) => e.toJson()).toList() ?? [], }; } } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index c253da02..4f0fff01 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.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/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateSpaceModelDialog extends StatelessWidget { @@ -48,37 +50,58 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ), const SizedBox(height: 16), - SizedBox( - width: screenWidth * 0.25, - child: Container( - decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, - border: Border.all( - color: ColorsManager.neutralGray, - width: 3.0, + GestureDetector( + onTap: () async { + final result = await showDialog( + context: context, + builder: (BuildContext context) { + return CreateSubSpaceModelDialog( + isEdit: true, + dialogTitle: 'Create Sub-space', + existingSubSpaces: [ + SubspaceTemplateModel( + subspaceName: "Living Room", + disabled: false, + uuid: "mkmkl,", + ), + ], + ); + }, + ); + if (result == true) {} + }, + child: SizedBox( + width: screenWidth * 0.25, + child: Container( + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + border: Border.all( + color: ColorsManager.neutralGray, + width: 3.0, + ), + borderRadius: BorderRadius.circular(20), ), - borderRadius: BorderRadius.circular(20), - ), - child: const Padding( - padding: - EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), - child: Row( - children: [ - Icon( - Icons.add, - color: ColorsManager.spaceColor, - ), - SizedBox(width: 10), - Expanded( - child: Text( - 'Create sub space', - style: TextStyle( - color: ColorsManager.blackColor, - fontSize: 16, + child: const Padding( + padding: + EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), + child: Row( + children: [ + Icon( + Icons.add, + color: ColorsManager.spaceColor, + ), + SizedBox(width: 10), + Expanded( + child: Text( + 'Create sub space', + style: TextStyle( + color: ColorsManager.blackColor, + fontSize: 16, + ), ), ), - ), - ], + ], + ), ), ), ), diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart new file mode 100644 index 00000000..9738f079 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart @@ -0,0 +1,188 @@ +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/space_model/bloc/subspace_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class CreateSubSpaceModelDialog extends StatefulWidget { + final bool isEdit; // Flag to determine if it's edit or create + final String dialogTitle; // Title for the dialog + final List? existingSubSpaces; // For edit mode + + const CreateSubSpaceModelDialog({ + super.key, + required this.isEdit, + required this.dialogTitle, + this.existingSubSpaces, + }); + + @override + _CreateSubSpaceModelDialogState createState() => + _CreateSubSpaceModelDialogState(); +} + +class _CreateSubSpaceModelDialogState extends State { + final TextEditingController textController = TextEditingController(); + final FocusNode focusNode = FocusNode(); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + focusNode.requestFocus(); + }); + + if (widget.isEdit) {} + } + + @override + void dispose() { + textController.dispose(); + focusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + child: BlocProvider( + create: (_) => SubSpaceModelBloc(), + child: BlocBuilder( + builder: (context, state) { + return SizedBox( + width: screenWidth * 0.35, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + widget.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.map( + (subSpace) => Chip( + label: Text(subSpace.subspaceName, + style: const TextStyle( + color: ColorsManager.spaceColor)), + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: const BorderSide( + color: 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, + focusNode: focusNode, + decoration: InputDecoration( + border: InputBorder.none, + hintText: state.subSpaces.isEmpty + ? 'Please enter the name' + : null, + hintStyle: const TextStyle( + color: ColorsManager.lightGrayColor), + ), + onSubmitted: (value) { + if (value.trim().isNotEmpty) { + context.read().add( + AddSubSpaceModel(SubspaceTemplateModel( + subspaceName: value.trim(), + disabled: false))); + textController.clear(); + focusNode.requestFocus(); + } + }, + style: const TextStyle( + color: ColorsManager.blackColor), + ), + ), + ], + ), + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: CancelButton( + label: 'Cancel', + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + onPressed: () { + final subSpaces = context + .read() + .state + .subSpaces; + Navigator.of(context).pop(subSpaces); + }, + backgroundColor: ColorsManager.secondaryColor, + borderRadius: 10, + foregroundColor: ColorsManager.whiteColors, + child: const Text('OK'), + ), + ), + ], + ), + ], + ), + ), + ); + }, + ), + ), + ); + } +} 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 49128a3f..550361c7 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 @@ -19,9 +19,11 @@ class SpaceModelCardWidget extends StatelessWidget { } for (var subspace in model.subspaceModels) { - for (var tag in subspace.tags) { - final prodIcon = tag.product?.icon ?? 'Unknown'; - productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + if (subspace.tags != null) { + for (var tag in subspace.tags!) { + final prodIcon = tag.product?.icon ?? 'Unknown'; + productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + } } } diff --git a/lib/utils/constants/temp_const.dart b/lib/utils/constants/temp_const.dart index e5847b98..bcd1a1d6 100644 --- a/lib/utils/constants/temp_const.dart +++ b/lib/utils/constants/temp_const.dart @@ -1,3 +1,3 @@ class TempConst { - static const projectId = '0e62577c-06fa-41b9-8a92-99a21fbaf51c'; + static const projectId = '0685c781-df33-4cbf-bf65-9f4e835eb468'; } From a98f7e77a35dbbe51cb2c035548384b133600c50 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Sat, 4 Jan 2025 17:45:15 +0300 Subject: [PATCH 033/175] Implemented side tree to devices and rountines screen --- .../{ => widgets}/custom_expansion_tile.dart | 8 +- lib/common/{ => widgets}/search_bar.dart | 0 lib/common/widgets/spaces_side_tree.dart | 25 ++ lib/main.dart | 13 +- .../device_managment_bloc.dart | 76 ++-- .../device_managment_event.dart | 9 +- .../all_devices/models/devices_model.dart | 44 +-- .../view/device_managment_page.dart | 8 +- .../widgets/device_managment_body.dart | 228 ++++++----- .../widgets/device_search_filters.dart | 11 +- lib/pages/home/bloc/home_bloc.dart | 42 +- lib/pages/home/bloc/home_event.dart | 20 +- lib/pages/home/bloc/home_state.dart | 24 +- lib/pages/home/view/home_page_mobile.dart | 67 ++-- lib/pages/home/view/home_page_web.dart | 73 ++-- lib/pages/home/view/tree_page.dart | 362 +++++++++--------- .../dialog_helper/device_dialog_helper.dart | 85 ---- lib/pages/routiens/view/routines_view.dart | 69 ---- .../fetch_routine_scenes_automation.dart | 143 ------- .../effective_period/effect_period_bloc.dart | 4 +- .../effective_period/effect_period_event.dart | 2 +- .../effective_period/effect_period_state.dart | 0 .../functions_bloc/functions_bloc_bloc.dart | 11 +- .../functions_bloc/functions_bloc_event.dart | 0 .../functions_bloc/functions_bloc_state.dart | 0 .../bloc/routine_bloc/routine_bloc.dart | 90 ++--- .../bloc/routine_bloc/routine_event.dart | 12 +- .../bloc/routine_bloc/routine_state.dart | 0 .../bloc/setting_bloc/setting_bloc.dart | 6 +- .../bloc/setting_bloc/setting_event.dart | 0 .../bloc/setting_bloc/setting_state.dart | 2 +- .../dialog_helper/device_dialog_helper.dart | 62 +++ .../helper/duration_format_helper.dart | 0 .../helper/save_routine_helper.dart | 6 +- .../models/ac/ac_function.dart | 4 +- .../models/ac/ac_operational_value.dart | 0 .../create_automation_model.dart | 0 .../create_scene_model.dart | 0 .../models/delay/delay_fucntions.dart | 4 +- .../models/device_functions.dart | 0 .../gang_switches/base_switch_function.dart | 4 +- .../one_gang_switch/one_gang_switch.dart | 4 +- .../switch_operational_value.dart | 0 .../three_gang_switch/three_gang_switch.dart | 13 +- .../two_gang_switch/two_gang_switch.dart | 10 +- .../models/icon_model.dart | 0 .../models/routine_details_model.dart | 4 +- .../models/routine_item.dart | 0 .../models/routine_model.dart | 0 .../view/create_new_routine_view.dart | 10 +- .../view/effective_period_view.dart | 6 +- lib/pages/routines/view/routines_view.dart | 95 +++++ .../conditions_routines_devices_view.dart | 12 +- .../widgets/delete_scene.dart | 2 +- .../widgets/dialog_footer.dart | 0 .../widgets/dialog_header.dart | 0 .../widgets/dragable_card.dart | 4 +- .../widgets/if_container.dart | 80 ++-- .../fetch_routine_scenes_automation.dart | 143 +++++++ .../main_routine_view/routine_view_card.dart | 6 +- .../widgets/period_option.dart | 8 +- .../widgets/repeat_days.dart | 6 +- .../widgets/routine_devices.dart | 8 +- .../widgets/routine_dialogs/ac_dialog.dart | 14 +- .../routine_dialogs/automation_dialog.dart | 17 +- .../widgets/routine_dialogs/delay_dialog.dart | 8 +- .../routine_dialogs/discard_dialog.dart | 2 +- .../effictive_period_dialog.dart | 4 +- .../one_gang_switch_dialog.dart | 61 ++- .../routine_dialogs/setting_dialog.dart | 24 +- .../three_gang_switch_dialog.dart | 61 ++- .../two_gang_switch_dialog.dart | 64 ++-- .../widgets/routine_search_and_buttons.dart | 8 +- .../widgets/routines_title_widget.dart | 0 .../widgets/scenes_and_automations.dart | 14 +- .../widgets/search_bar_condition_title.dart | 4 +- .../widgets/then_container.dart | 10 +- .../space_tree/bloc/space_tree_bloc.dart | 69 ++++ .../space_tree/bloc/space_tree_event.dart | 29 ++ .../space_tree/bloc/space_tree_state.dart | 31 ++ .../space_tree/view/side_spaces_view.dart | 26 ++ .../space_tree/view/space_tree_view.dart | 251 ++++++++++++ .../all_spaces/widgets/community_tile.dart | 4 +- .../all_spaces/widgets/sidebar_widget.dart | 27 +- .../all_spaces/widgets/space_tile_widget.dart | 2 +- lib/services/devices_mang_api.dart | 33 +- lib/services/routines_api.dart | 48 +-- lib/utils/constants/api_const.dart | 17 +- 88 files changed, 1551 insertions(+), 1202 deletions(-) rename lib/common/{ => widgets}/custom_expansion_tile.dart (95%) rename lib/common/{ => widgets}/search_bar.dart (100%) create mode 100644 lib/common/widgets/spaces_side_tree.dart delete mode 100644 lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart delete mode 100644 lib/pages/routiens/view/routines_view.dart delete mode 100644 lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart rename lib/pages/{routiens => routines}/bloc/effective_period/effect_period_bloc.dart (96%) rename lib/pages/{routiens => routines}/bloc/effective_period/effect_period_event.dart (95%) rename lib/pages/{routiens => routines}/bloc/effective_period/effect_period_state.dart (100%) rename lib/pages/{routiens => routines}/bloc/functions_bloc/functions_bloc_bloc.dart (86%) rename lib/pages/{routiens => routines}/bloc/functions_bloc/functions_bloc_event.dart (100%) rename lib/pages/{routiens => routines}/bloc/functions_bloc/functions_bloc_state.dart (100%) rename lib/pages/{routiens => routines}/bloc/routine_bloc/routine_bloc.dart (96%) rename lib/pages/{routiens => routines}/bloc/routine_bloc/routine_event.dart (95%) rename lib/pages/{routiens => routines}/bloc/routine_bloc/routine_state.dart (100%) rename lib/pages/{routiens => routines}/bloc/setting_bloc/setting_bloc.dart (89%) rename lib/pages/{routiens => routines}/bloc/setting_bloc/setting_event.dart (100%) rename lib/pages/{routiens => routines}/bloc/setting_bloc/setting_state.dart (94%) create mode 100644 lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart rename lib/pages/{routiens => routines}/helper/duration_format_helper.dart (100%) rename lib/pages/{routiens => routines}/helper/save_routine_helper.dart (97%) rename lib/pages/{routiens => routines}/models/ac/ac_function.dart (96%) rename lib/pages/{routiens => routines}/models/ac/ac_operational_value.dart (100%) rename lib/pages/{routiens => routines}/models/create_scene_and_autoamtion/create_automation_model.dart (100%) rename lib/pages/{routiens => routines}/models/create_scene_and_autoamtion/create_scene_model.dart (100%) rename lib/pages/{routiens => routines}/models/delay/delay_fucntions.dart (85%) rename lib/pages/{routiens => routines}/models/device_functions.dart (100%) rename lib/pages/{routiens => routines}/models/gang_switches/base_switch_function.dart (72%) rename lib/pages/{routiens => routines}/models/gang_switches/one_gang_switch/one_gang_switch.dart (91%) rename lib/pages/{routiens => routines}/models/gang_switches/switch_operational_value.dart (100%) rename lib/pages/{routiens => routines}/models/gang_switches/three_gang_switch/three_gang_switch.dart (89%) rename lib/pages/{routiens => routines}/models/gang_switches/two_gang_switch/two_gang_switch.dart (88%) rename lib/pages/{routiens => routines}/models/icon_model.dart (100%) rename lib/pages/{routiens => routines}/models/routine_details_model.dart (98%) rename lib/pages/{routiens => routines}/models/routine_item.dart (100%) rename lib/pages/{routiens => routines}/models/routine_model.dart (100%) rename lib/pages/{routiens => routines}/view/create_new_routine_view.dart (92%) rename lib/pages/{routiens => routines}/view/effective_period_view.dart (86%) create mode 100644 lib/pages/routines/view/routines_view.dart rename lib/pages/{routiens => routines}/widgets/conditions_routines_devices_view.dart (91%) rename lib/pages/{routiens => routines}/widgets/delete_scene.dart (97%) rename lib/pages/{routiens => routines}/widgets/dialog_footer.dart (100%) rename lib/pages/{routiens => routines}/widgets/dialog_header.dart (100%) rename lib/pages/{routiens => routines}/widgets/dragable_card.dart (98%) rename lib/pages/{routiens => routines}/widgets/if_container.dart (68%) create mode 100644 lib/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart rename lib/pages/{routiens => routines}/widgets/main_routine_view/routine_view_card.dart (96%) rename lib/pages/{routiens => routines}/widgets/period_option.dart (93%) rename lib/pages/{routiens => routines}/widgets/repeat_days.dart (94%) rename lib/pages/{routiens => routines}/widgets/routine_devices.dart (90%) rename lib/pages/{routiens => routines}/widgets/routine_dialogs/ac_dialog.dart (96%) rename lib/pages/{routiens => routines}/widgets/routine_dialogs/automation_dialog.dart (87%) rename lib/pages/{routiens => routines}/widgets/routine_dialogs/delay_dialog.dart (91%) rename lib/pages/{routiens => routines}/widgets/routine_dialogs/discard_dialog.dart (97%) rename lib/pages/{routiens => routines}/widgets/routine_dialogs/effictive_period_dialog.dart (97%) rename lib/pages/{routiens => routines}/widgets/routine_dialogs/one_gang_switch_dialog.dart (86%) rename lib/pages/{routiens => routines}/widgets/routine_dialogs/setting_dialog.dart (97%) rename lib/pages/{routiens => routines}/widgets/routine_dialogs/three_gang_switch_dialog.dart (86%) rename lib/pages/{routiens => routines}/widgets/routine_dialogs/two_gang_switch_dialog.dart (86%) rename lib/pages/{routiens => routines}/widgets/routine_search_and_buttons.dart (98%) rename lib/pages/{routiens => routines}/widgets/routines_title_widget.dart (100%) rename lib/pages/{routiens => routines}/widgets/scenes_and_automations.dart (80%) rename lib/pages/{routiens => routines}/widgets/search_bar_condition_title.dart (95%) rename lib/pages/{routiens => routines}/widgets/then_container.dart (96%) create mode 100644 lib/pages/space_tree/bloc/space_tree_bloc.dart create mode 100644 lib/pages/space_tree/bloc/space_tree_event.dart create mode 100644 lib/pages/space_tree/bloc/space_tree_state.dart create mode 100644 lib/pages/space_tree/view/side_spaces_view.dart create mode 100644 lib/pages/space_tree/view/space_tree_view.dart diff --git a/lib/common/custom_expansion_tile.dart b/lib/common/widgets/custom_expansion_tile.dart similarity index 95% rename from lib/common/custom_expansion_tile.dart rename to lib/common/widgets/custom_expansion_tile.dart index 8df9b663..b6b33479 100644 --- a/lib/common/custom_expansion_tile.dart +++ b/lib/common/widgets/custom_expansion_tile.dart @@ -58,16 +58,18 @@ class CustomExpansionTileState extends State { children: [ // Checkbox with independent state management Checkbox( - value: false, + value: widget.isSelected, onChanged: (bool? value) { - setState(() {}); + if (widget.onItemSelected != null) { + widget.onItemSelected!(); + } }, side: WidgetStateBorderSide.resolveWith((states) { return const BorderSide(color: ColorsManager.grayBorder); }), fillColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { - return ColorsManager.grayBorder; + return ColorsManager.blue1; } else { return ColorsManager.checkBoxFillColor; } diff --git a/lib/common/search_bar.dart b/lib/common/widgets/search_bar.dart similarity index 100% rename from lib/common/search_bar.dart rename to lib/common/widgets/search_bar.dart diff --git a/lib/common/widgets/spaces_side_tree.dart b/lib/common/widgets/spaces_side_tree.dart new file mode 100644 index 00000000..b77b0dc5 --- /dev/null +++ b/lib/common/widgets/spaces_side_tree.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; + +class SpacesSideTree extends StatefulWidget { + final List communities; + final String? selectedSpaceUuid; + const SpacesSideTree({ + super.key, + required this.communities, + this.selectedSpaceUuid, + }); + + @override + State createState() => _SpacesSideTreeState(); +} + +class _SpacesSideTreeState extends State { + String _searchQuery = ''; + String? _selectedSpaceUuid; + String? _selectedId; + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/lib/main.dart b/lib/main.dart index 2040d175..0bf0b9d3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,7 +6,9 @@ import 'package:go_router/go_router.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_event.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.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'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/utils/app_routes.dart'; @@ -15,8 +17,7 @@ import 'package:syncrow_web/utils/theme/theme.dart'; Future main() async { try { - const environment = - String.fromEnvironment('FLAVOR', defaultValue: 'development'); + const environment = String.fromEnvironment('FLAVOR', defaultValue: 'development'); await dotenv.load(fileName: '.env.$environment'); WidgetsFlutterBinding.ensureInitialized(); initialSetup(); @@ -48,14 +49,16 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ - BlocProvider( - create: (context) => HomeBloc()..add(const FetchUserInfo())), + BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())), BlocProvider( create: (context) => VisitorPasswordBloc(), ), BlocProvider( create: (context) => RoutineBloc(), ), + BlocProvider( + create: (context) => SpaceTreeBloc()..add(InitialEvent()), + ), ], child: MaterialApp.router( debugShowCheckedModeBanner: false, diff --git a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart index 8f6d085d..1fa61fc2 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart @@ -6,8 +6,7 @@ import 'package:syncrow_web/services/devices_mang_api.dart'; part 'device_managment_event.dart'; part 'device_managment_state.dart'; -class DeviceManagementBloc - extends Bloc { +class DeviceManagementBloc extends Bloc { int _selectedIndex = 0; List _devices = []; int _onlineCount = 0; @@ -18,6 +17,8 @@ class DeviceManagementBloc String currentProductName = ''; String? currentCommunity; String? currentUnitName; + String? communityId; + String? spaceId; DeviceManagementBloc() : super(DeviceManagementInitial()) { on(_onFetchDevices); @@ -30,11 +31,16 @@ class DeviceManagementBloc on(_onUpdateSelection); } - Future _onFetchDevices( - FetchDevices event, Emitter emit) async { + Future _onFetchDevices(FetchDevices event, Emitter emit) async { emit(DeviceManagementLoading()); try { - final devices = await DevicesManagementApi().fetchDevices(); + if (event.communityId.isNotEmpty) { + communityId = event.communityId; + } + if (event.spaceId.isNotEmpty) { + spaceId = event.spaceId; + } + final devices = await DevicesManagementApi().fetchDevices(communityId ?? '', spaceId ?? ''); _selectedDevices.clear(); _devices = devices; _filteredDevices = devices; @@ -53,8 +59,7 @@ class DeviceManagementBloc } } - void _onFilterDevices( - FilterDevices event, Emitter emit) async { + void _onFilterDevices(FilterDevices event, Emitter emit) async { if (_devices.isNotEmpty) { _filteredDevices = List.from(_devices.where((device) { switch (event.filter) { @@ -85,8 +90,7 @@ class DeviceManagementBloc } } - Future _onResetFilters( - ResetFilters event, Emitter emit) async { + Future _onResetFilters(ResetFilters event, Emitter emit) async { currentProductName = ''; _selectedDevices.clear(); _filteredDevices = List.from(_devices); @@ -102,8 +106,7 @@ class DeviceManagementBloc )); } - void _onResetSelectedDevices( - ResetSelectedDevices event, Emitter emit) { + void _onResetSelectedDevices(ResetSelectedDevices event, Emitter emit) { _selectedDevices.clear(); if (state is DeviceManagementLoaded) { @@ -129,14 +132,12 @@ class DeviceManagementBloc } } - void _onSelectedFilterChanged( - SelectedFilterChanged event, Emitter emit) { + void _onSelectedFilterChanged(SelectedFilterChanged event, Emitter emit) { _selectedIndex = event.selectedIndex; add(FilterDevices(_getFilterFromIndex(_selectedIndex))); } - void _onSelectDevice( - SelectDevice event, Emitter emit) { + void _onSelectDevice(SelectDevice event, Emitter emit) { final selectedUuid = event.selectedDevice.uuid; if (_selectedDevices.any((device) => device.uuid == selectedUuid)) { @@ -147,8 +148,7 @@ class DeviceManagementBloc List clonedSelectedDevices = List.from(_selectedDevices); - bool isControlButtonEnabled = - _checkIfControlButtonEnabled(clonedSelectedDevices); + bool isControlButtonEnabled = _checkIfControlButtonEnabled(clonedSelectedDevices); if (state is DeviceManagementLoaded) { emit(DeviceManagementLoaded( @@ -157,8 +157,7 @@ class DeviceManagementBloc onlineCount: _onlineCount, offlineCount: _offlineCount, lowBatteryCount: _lowBatteryCount, - selectedDevice: - clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null, + selectedDevice: clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null, isControlButtonEnabled: isControlButtonEnabled, )); } else if (state is DeviceManagementFiltered) { @@ -168,15 +167,13 @@ class DeviceManagementBloc onlineCount: _onlineCount, offlineCount: _offlineCount, lowBatteryCount: _lowBatteryCount, - selectedDevice: - clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null, + selectedDevice: clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null, isControlButtonEnabled: isControlButtonEnabled, )); } } - void _onUpdateSelection( - UpdateSelection event, Emitter emit) { + void _onUpdateSelection(UpdateSelection event, Emitter emit) { List selectedDevices = []; List devicesToSelectFrom = []; @@ -219,8 +216,7 @@ class DeviceManagementBloc bool _checkIfControlButtonEnabled(List selectedDevices) { if (selectedDevices.length > 1) { - final productTypes = - selectedDevices.map((device) => device.productType).toSet(); + final productTypes = selectedDevices.map((device) => device.productType).toSet(); return productTypes.length == 1; } else if (selectedDevices.length == 1) { return true; @@ -231,10 +227,8 @@ class DeviceManagementBloc void _calculateDeviceCounts() { _onlineCount = _devices.where((device) => device.online == true).length; _offlineCount = _devices.where((device) => device.online == false).length; - _lowBatteryCount = _devices - .where((device) => - device.batteryLevel != null && device.batteryLevel! < 20) - .length; + _lowBatteryCount = + _devices.where((device) => device.batteryLevel != null && device.batteryLevel! < 20).length; } String _getFilterFromIndex(int index) { @@ -250,8 +244,7 @@ class DeviceManagementBloc } } - void _onSearchDevices( - SearchDevices event, Emitter emit) { + void _onSearchDevices(SearchDevices event, Emitter emit) { if ((event.community == null || event.community!.isEmpty) && (event.unitName == null || event.unitName!.isEmpty) && (event.productName == null || event.productName!.isEmpty)) { @@ -280,33 +273,22 @@ class DeviceManagementBloc final filteredDevices = devicesToSearch.where((device) { final matchesCommunity = event.community == null || event.community!.isEmpty || - (device.community?.name - ?.toLowerCase() - .contains(event.community!.toLowerCase()) ?? + (device.community?.name?.toLowerCase().contains(event.community!.toLowerCase()) ?? false); final matchesUnit = event.unitName == null || event.unitName!.isEmpty || (device.spaces != null && device.spaces!.isNotEmpty && - device.spaces![0].spaceName - !.toLowerCase() - .contains(event.unitName!.toLowerCase())); + device.spaces![0].spaceName!.toLowerCase().contains(event.unitName!.toLowerCase())); final matchesProductName = event.productName == null || event.productName!.isEmpty || - (device.name - ?.toLowerCase() - .contains(event.productName!.toLowerCase()) ?? - false); + (device.name?.toLowerCase().contains(event.productName!.toLowerCase()) ?? false); final matchesDeviceName = event.productName == null || event.productName!.isEmpty || - (device.categoryName - ?.toLowerCase() - .contains(event.productName!.toLowerCase()) ?? + (device.categoryName?.toLowerCase().contains(event.productName!.toLowerCase()) ?? false); - return matchesCommunity && - matchesUnit && - (matchesProductName || matchesDeviceName); + return matchesCommunity && matchesUnit && (matchesProductName || matchesDeviceName); }).toList(); emit(DeviceManagementFiltered( diff --git a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_event.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_event.dart index c7509080..da52249c 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_event.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_event.dart @@ -7,7 +7,14 @@ abstract class DeviceManagementEvent extends Equatable { List get props => []; } -class FetchDevices extends DeviceManagementEvent {} +class FetchDevices extends DeviceManagementEvent { + final String communityId; + final String spaceId; + + const FetchDevices(this.communityId, this.spaceId); + @override + List get props => [communityId, spaceId]; +} class FilterDevices extends DeviceManagementEvent { final String filter; diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index b7e4f010..d30afc03 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -2,11 +2,11 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_com import 'package:syncrow_web/pages/device_managment/all_devices/models/device_space_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart'; -import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart'; -import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; -import 'package:syncrow_web/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart'; -import 'package:syncrow_web/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart'; -import 'package:syncrow_web/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart'; +import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart'; +import 'package:syncrow_web/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart'; +import 'package:syncrow_web/pages/routines/models/gang_switches/two_gang_switch/two_gang_switch.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/enum/device_types.dart'; @@ -142,9 +142,7 @@ class AllDevicesModel { productName = json['productName']?.toString(); if (json['spaces'] != null && json['spaces'] is List) { - spaces = (json['spaces'] as List) - .map((space) => DeviceSpaceModel.fromJson(space)) - .toList(); + spaces = (json['spaces'] as List).map((space) => DeviceSpaceModel.fromJson(space)).toList(); } } @@ -192,8 +190,7 @@ SOS String tempIcon = ''; if (type == DeviceType.LightBulb) { tempIcon = Assets.lightBulb; - } else if (type == DeviceType.CeilingSensor || - type == DeviceType.WallSensor) { + } else if (type == DeviceType.CeilingSensor || type == DeviceType.WallSensor) { tempIcon = Assets.sensors; } else if (type == DeviceType.AC) { tempIcon = Assets.ac; @@ -248,34 +245,25 @@ SOS case '1G': return [ OneGangSwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''), - OneGangCountdownFunction( - deviceId: uuid ?? '', deviceName: name ?? ''), + OneGangCountdownFunction(deviceId: uuid ?? '', deviceName: name ?? ''), ]; case '2G': return [ TwoGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''), TwoGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''), - TwoGangCountdown1Function( - deviceId: uuid ?? '', deviceName: name ?? ''), - TwoGangCountdown2Function( - deviceId: uuid ?? '', deviceName: name ?? ''), + TwoGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? ''), + TwoGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? ''), ]; case '3G': return [ - ThreeGangSwitch1Function( - deviceId: uuid ?? '', deviceName: name ?? ''), - ThreeGangSwitch2Function( - deviceId: uuid ?? '', deviceName: name ?? ''), - ThreeGangSwitch3Function( - deviceId: uuid ?? '', deviceName: name ?? ''), - ThreeGangCountdown1Function( - deviceId: uuid ?? '', deviceName: name ?? ''), - ThreeGangCountdown2Function( - deviceId: uuid ?? '', deviceName: name ?? ''), - ThreeGangCountdown3Function( - deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangSwitch3Function(deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangCountdown3Function(deviceId: uuid ?? '', deviceName: name ?? ''), ]; default: diff --git a/lib/pages/device_managment/all_devices/view/device_managment_page.dart b/lib/pages/device_managment/all_devices/view/device_managment_page.dart index e8cbd4ca..f64ef734 100644 --- a/lib/pages/device_managment/all_devices/view/device_managment_page.dart +++ b/lib/pages/device_managment/all_devices/view/device_managment_page.dart @@ -3,9 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart'; -import 'package:syncrow_web/pages/routiens/view/routines_view.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart'; +import 'package:syncrow_web/pages/routines/view/routines_view.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; @@ -19,7 +19,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { return MultiBlocProvider( providers: [ BlocProvider( - create: (context) => DeviceManagementBloc()..add(FetchDevices()), + create: (context) => DeviceManagementBloc()..add(const FetchDevices('', '')), ), ], child: WebScaffold( diff --git a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart index 0788e08d..9ae3b89f 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart @@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart'; +import 'package:syncrow_web/pages/space_tree/view/side_spaces_view.dart'; import 'package:syncrow_web/utils/format_date_time.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -59,118 +60,143 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { final buttonLabel = (selectedDevices.length > 1) ? 'Batch Control' : 'Control'; - return Column( + return Row( children: [ - Container( - padding: isLargeScreenSize(context) ? const EdgeInsets.all(30) : const EdgeInsets.all(15), + Flexible(child: SideSpacesView( + onSelectAction: (String communityId, String spaceId) { + context.read().add(FetchDevices(communityId, spaceId)); + }, + )), + Flexible( + flex: 3, child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - FilterWidget( - size: MediaQuery.of(context).size, - tabs: tabs, - selectedIndex: selectedIndex, - onTabChanged: (index) { - context.read().add(SelectedFilterChanged(index)); - }, - ), - const SizedBox(height: 20), - const DeviceSearchFilters(), - const SizedBox(height: 12), Container( - height: 45, - width: 125, - decoration: containerDecoration, - child: Center( - child: DefaultButton( - onPressed: isControlButtonEnabled - ? () { - if (selectedDevices.length == 1) { - showDialog( - context: context, - builder: (context) => DeviceControlDialog( - device: selectedDevices.first, - ), - ); - } else if (selectedDevices.length > 1) { - final productTypes = selectedDevices.map((device) => device.productType).toSet(); - if (productTypes.length == 1) { - showDialog( - context: context, - builder: (context) => DeviceBatchControlDialog( - devices: selectedDevices, - ), - ); - } - } - } - : null, - borderRadius: 9, - child: Text( - buttonLabel, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - color: isControlButtonEnabled ? Colors.white : Colors.grey, + padding: isLargeScreenSize(context) + ? const EdgeInsets.all(30) + : const EdgeInsets.all(15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FilterWidget( + size: MediaQuery.of(context).size, + tabs: tabs, + selectedIndex: selectedIndex, + onTabChanged: (index) { + context.read().add(SelectedFilterChanged(index)); + }, + ), + const SizedBox(height: 20), + const DeviceSearchFilters(), + const SizedBox(height: 12), + Container( + height: 45, + width: 125, + decoration: containerDecoration, + child: Center( + child: DefaultButton( + onPressed: isControlButtonEnabled + ? () { + if (selectedDevices.length == 1) { + showDialog( + context: context, + builder: (context) => DeviceControlDialog( + device: selectedDevices.first, + ), + ); + } else if (selectedDevices.length > 1) { + final productTypes = selectedDevices + .map((device) => device.productType) + .toSet(); + if (productTypes.length == 1) { + showDialog( + context: context, + builder: (context) => DeviceBatchControlDialog( + devices: selectedDevices, + ), + ); + } + } + } + : null, + borderRadius: 9, + child: Text( + buttonLabel, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: isControlButtonEnabled ? Colors.white : Colors.grey, + ), + ), + ), ), ), - ), + ], ), ), + Expanded( + child: Padding( + padding: isLargeScreenSize(context) + ? const EdgeInsets.all(30) + : const EdgeInsets.all(15), + child: DynamicTable( + withSelectAll: true, + cellDecoration: containerDecoration, + onRowSelected: (index, isSelected, row) { + final selectedDevice = devicesToShow[index]; + context.read().add(SelectDevice(selectedDevice)); + }, + withCheckBox: true, + size: MediaQuery.of(context).size, + uuidIndex: 2, + headers: const [ + 'Device Name', + 'Product Name', + 'Device ID', + 'Space Name', + 'location', + 'Battery Level', + 'Installation Date and Time', + 'Status', + 'Last Offline Date and Time', + ], + data: devicesToShow.map((device) { + final combinedSpaceNames = device.spaces != null + ? device.spaces!.map((space) => space.spaceName).join(' > ') + + (device.community != null ? ' > ${device.community!.name}' : '') + : (device.community != null ? device.community!.name : ''); + + return [ + device.name ?? '', + device.productName ?? '', + device.uuid ?? '', + (device.spaces != null && device.spaces!.isNotEmpty) + ? device.spaces![0].spaceName + : '', + combinedSpaceNames, + device.batteryLevel != null ? '${device.batteryLevel}%' : '-', + formatDateTime(DateTime.fromMillisecondsSinceEpoch( + (device.createTime ?? 0) * 1000)), + device.online == true ? 'Online' : 'Offline', + formatDateTime(DateTime.fromMillisecondsSinceEpoch( + (device.updateTime ?? 0) * 1000)), + ]; + }).toList(), + onSelectionChanged: (selectedRows) { + context.read().add(UpdateSelection(selectedRows)); + }, + initialSelectedIds: context + .read() + .selectedDevices + .map((device) => device.uuid!) + .toList(), + isEmpty: devicesToShow.isEmpty, + ), + ), + ) ], ), ), - Expanded( - child: Padding( - padding: isLargeScreenSize(context) ? const EdgeInsets.all(30) : const EdgeInsets.all(15), - child: DynamicTable( - withSelectAll: true, - cellDecoration: containerDecoration, - onRowSelected: (index, isSelected, row) { - final selectedDevice = devicesToShow[index]; - context.read().add(SelectDevice(selectedDevice)); - }, - withCheckBox: true, - size: MediaQuery.of(context).size, - uuidIndex: 2, - headers: const [ - 'Device Name', - 'Product Name', - 'Device ID', - 'Space Name', - 'location', - 'Battery Level', - 'Installation Date and Time', - 'Status', - 'Last Offline Date and Time', - ], - data: devicesToShow.map((device) { - final combinedSpaceNames = device.spaces != null - ? device.spaces!.map((space) => space.spaceName).join(' > ') + - (device.community != null ? ' > ${device.community!.name}' : '') - : (device.community != null ? device.community!.name : ''); - - return [ - device.name ?? '', - device.productName ?? '', - device.uuid ?? '', - (device.spaces != null && device.spaces!.isNotEmpty) ? device.spaces![0].spaceName : '', - combinedSpaceNames, - device.batteryLevel != null ? '${device.batteryLevel}%' : '-', - formatDateTime(DateTime.fromMillisecondsSinceEpoch((device.createTime ?? 0) * 1000)), - device.online == true ? 'Online' : 'Offline', - formatDateTime(DateTime.fromMillisecondsSinceEpoch((device.updateTime ?? 0) * 1000)), - ]; - }).toList(), - onSelectionChanged: (selectedRows) { - context.read().add(UpdateSelection(selectedRows)); - }, - initialSelectedIds: - context.read().selectedDevices.map((device) => device.uuid!).toList(), - isEmpty: devicesToShow.isEmpty, - ), - ), - ) ], ); }, diff --git a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart index b9e36c25..0f86ef15 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart @@ -12,8 +12,7 @@ class DeviceSearchFilters extends StatefulWidget { State createState() => _DeviceSearchFiltersState(); } -class _DeviceSearchFiltersState extends State - with HelperResponsiveLayout { +class _DeviceSearchFiltersState extends State with HelperResponsiveLayout { final TextEditingController communityController = TextEditingController(); final TextEditingController unitNameController = TextEditingController(); final TextEditingController productNameController = TextEditingController(); @@ -27,8 +26,7 @@ class _DeviceSearchFiltersState extends State const SizedBox(width: 20), _buildSearchField("Space Name", unitNameController, 200), const SizedBox(width: 20), - _buildSearchField( - "Device Name / Product Name", productNameController, 300), + _buildSearchField("Device Name / Product Name", productNameController, 300), const SizedBox(width: 20), _buildSearchResetButtons(), ], @@ -53,8 +51,7 @@ class _DeviceSearchFiltersState extends State ); } - Widget _buildSearchField( - String title, TextEditingController controller, double width) { + Widget _buildSearchField(String title, TextEditingController controller, double width) { return Container( child: StatefulTextField( title: title, @@ -88,7 +85,7 @@ class _DeviceSearchFiltersState extends State productNameController.clear(); context.read() ..add(ResetFilters()) - ..add(FetchDevices()); + ..add(const FetchDevices('', '')); }, ); } diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index 1772ef88..6863236b 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -1,44 +1,44 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:go_router/go_router.dart'; -import 'package:graphview/GraphView.dart'; +// import 'package:graphview/GraphView.dart'; import 'package:syncrow_web/pages/auth/model/user_model.dart'; import 'package:syncrow_web/pages/home/bloc/home_event.dart'; import 'package:syncrow_web/pages/home/bloc/home_state.dart'; import 'package:syncrow_web/pages/home/home_model/home_item_model.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/services/home_api.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/routes_const.dart'; class HomeBloc extends Bloc { - final Graph graph = Graph()..isTree = true; - final BuchheimWalkerConfiguration builder = BuchheimWalkerConfiguration(); - List sourcesList = []; - List destinationsList = []; + // final Graph graph = Graph()..isTree = true; + // final BuchheimWalkerConfiguration builder = BuchheimWalkerConfiguration(); + // List sourcesList = []; + // List destinationsList = []; UserModel? user; HomeBloc() : super((HomeInitial())) { - on(_createNode); + // on(_createNode); on(_fetchUserInfo); } - void _createNode(CreateNewNode event, Emitter emit) async { - emit(HomeInitial()); - sourcesList.add(event.sourceNode); - destinationsList.add(event.destinationNode); - for (int i = 0; i < sourcesList.length; i++) { - graph.addEdge(sourcesList[i], destinationsList[i]); - } + // void _createNode(CreateNewNode event, Emitter emit) async { + // emit(HomeInitial()); + // sourcesList.add(event.sourceNode); + // destinationsList.add(event.destinationNode); + // for (int i = 0; i < sourcesList.length; i++) { + // graph.addEdge(sourcesList[i], destinationsList[i]); + // } - builder - ..siblingSeparation = (100) - ..levelSeparation = (150) - ..subtreeSeparation = (150) - ..orientation = (BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM); - emit(HomeUpdateTree(graph: graph, builder: builder)); - } + // builder + // ..siblingSeparation = (100) + // ..levelSeparation = (150) + // ..subtreeSeparation = (150) + // ..orientation = (BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM); + // emit(HomeUpdateTree(graph: graph, builder: builder)); + // } Future _fetchUserInfo(FetchUserInfo event, Emitter emit) async { try { diff --git a/lib/pages/home/bloc/home_event.dart b/lib/pages/home/bloc/home_event.dart index 963202b9..50480602 100644 --- a/lib/pages/home/bloc/home_event.dart +++ b/lib/pages/home/bloc/home_event.dart @@ -1,5 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:graphview/GraphView.dart'; +// import 'package:graphview/GraphView.dart'; abstract class HomeEvent extends Equatable { const HomeEvent(); @@ -8,16 +8,16 @@ abstract class HomeEvent extends Equatable { List get props => []; } -class CreateNewNode extends HomeEvent { - final Node sourceNode; - final Node destinationNode; - const CreateNewNode( - {required this.sourceNode, required this.destinationNode}); +// class CreateNewNode extends HomeEvent { +// final Node sourceNode; +// final Node destinationNode; +// const CreateNewNode( +// {required this.sourceNode, required this.destinationNode}); - @override - List get props => [sourceNode, destinationNode]; -} +// @override +// List get props => [sourceNode, destinationNode]; +// } class FetchUserInfo extends HomeEvent { const FetchUserInfo(); -} \ No newline at end of file +} diff --git a/lib/pages/home/bloc/home_state.dart b/lib/pages/home/bloc/home_state.dart index 10c50486..64c840ab 100644 --- a/lib/pages/home/bloc/home_state.dart +++ b/lib/pages/home/bloc/home_state.dart @@ -1,5 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:graphview/GraphView.dart'; +// import 'package:graphview/GraphView.dart'; abstract class HomeState extends Equatable { const HomeState(); @@ -10,17 +10,17 @@ abstract class HomeState extends Equatable { class HomeInitial extends HomeState {} -class HomeCounterState extends HomeState { - final int counter; - const HomeCounterState(this.counter); -} +// class HomeCounterState extends HomeState { +// final int counter; +// const HomeCounterState(this.counter); +// } -class HomeUpdateTree extends HomeState { - final Graph graph; - final BuchheimWalkerConfiguration builder; +// class HomeUpdateTree extends HomeState { +// final Graph graph; +// final BuchheimWalkerConfiguration builder; - const HomeUpdateTree({required this.graph, required this.builder}); +// const HomeUpdateTree({required this.graph, required this.builder}); - @override - List get props => [graph, builder]; -} +// @override +// List get props => [graph, builder]; +// } diff --git a/lib/pages/home/view/home_page_mobile.dart b/lib/pages/home/view/home_page_mobile.dart index 8f72f8cb..17735409 100644 --- a/lib/pages/home/view/home_page_mobile.dart +++ b/lib/pages/home/view/home_page_mobile.dart @@ -41,8 +41,7 @@ class HomeMobilePage extends StatelessWidget { SizedBox(height: size.height * 0.05), const Text( 'ACCESS YOUR APPS', - style: - TextStyle(fontSize: 20, fontWeight: FontWeight.w700), + style: TextStyle(fontSize: 20, fontWeight: FontWeight.w700), ), const SizedBox(height: 30), Expanded( @@ -51,9 +50,8 @@ class HomeMobilePage extends StatelessWidget { height: size.height * 0.6, width: size.width * 0.68, child: GridView.builder( - itemCount: 8, - gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( + itemCount: 3, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, @@ -65,8 +63,7 @@ class HomeMobilePage extends StatelessWidget { active: homeItems[index]['active'], name: homeItems[index]['title'], img: homeItems[index]['icon'], - onTap: () => - homeBloc.homeItems[index].onPress(context), + onTap: () => homeBloc.homeItems[index].onPress(context), ); }, ), @@ -97,33 +94,33 @@ class HomeMobilePage extends StatelessWidget { 'icon': Assets.devicesIcon, 'active': true, }, - { - 'title': 'Move in', - 'icon': Assets.moveinIcon, - 'active': false, - }, - { - 'title': 'Construction', - 'icon': Assets.constructionIcon, - 'active': false, - }, - { - 'title': 'Energy', - 'icon': Assets.energyIcon, - 'color': ColorsManager.slidingBlueColor.withOpacity(0.2), - 'active': false, - }, - { - 'title': 'Integrations', - 'icon': Assets.integrationsIcon, - 'color': ColorsManager.slidingBlueColor.withOpacity(0.2), - 'active': false, - }, - { - 'title': 'Asset', - 'icon': Assets.assetIcon, - 'color': ColorsManager.slidingBlueColor.withOpacity(0.2), - 'active': false, - }, + // { + // 'title': 'Move in', + // 'icon': Assets.moveinIcon, + // 'active': false, + // }, + // { + // 'title': 'Construction', + // 'icon': Assets.constructionIcon, + // 'active': false, + // }, + // { + // 'title': 'Energy', + // 'icon': Assets.energyIcon, + // 'color': ColorsManager.slidingBlueColor.withOpacity(0.2), + // 'active': false, + // }, + // { + // 'title': 'Integrations', + // 'icon': Assets.integrationsIcon, + // 'color': ColorsManager.slidingBlueColor.withOpacity(0.2), + // 'active': false, + // }, + // { + // 'title': 'Asset', + // 'icon': Assets.assetIcon, + // 'color': ColorsManager.slidingBlueColor.withOpacity(0.2), + // 'active': false, + // }, ]; } diff --git a/lib/pages/home/view/home_page_web.dart b/lib/pages/home/view/home_page_web.dart index a198fa76..866f9766 100644 --- a/lib/pages/home/view/home_page_web.dart +++ b/lib/pages/home/view/home_page_web.dart @@ -32,43 +32,48 @@ class HomeWebPage extends StatelessWidget { scaffoldBody: SizedBox( height: size.height, width: size.width, - child: Column( + child: Row( mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, children: [ - SizedBox(height: size.height * 0.1), - Text( - 'ACCESS YOUR APPS', - style: Theme.of(context) - .textTheme - .headlineLarge! - .copyWith(color: Colors.black, fontSize: 40), - ), - const SizedBox(height: 30), - Expanded( - flex: 4, - child: SizedBox( - height: size.height * 0.6, - width: size.width * 0.68, - child: GridView.builder( - itemCount: 3, //8 - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, - crossAxisSpacing: 20.0, - mainAxisSpacing: 20.0, - childAspectRatio: 1.5, - ), - itemBuilder: (context, index) { - return HomeCard( - index: index, - active: homeBloc.homeItems[index].active!, - name: homeBloc.homeItems[index].title!, - img: homeBloc.homeItems[index].icon!, - onTap: () => homeBloc.homeItems[index].onPress(context), - ); - }, + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: size.height * 0.1), + Text( + 'ACCESS YOUR APPS', + style: Theme.of(context) + .textTheme + .headlineLarge! + .copyWith(color: Colors.black, fontSize: 40), ), - ), + const SizedBox(height: 30), + Expanded( + flex: 4, + child: SizedBox( + height: size.height * 0.6, + width: size.width * 0.68, + child: GridView.builder( + itemCount: 3, //8 + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + crossAxisSpacing: 20.0, + mainAxisSpacing: 20.0, + childAspectRatio: 1.5, + ), + itemBuilder: (context, index) { + return HomeCard( + index: index, + active: homeBloc.homeItems[index].active!, + name: homeBloc.homeItems[index].title!, + img: homeBloc.homeItems[index].icon!, + onTap: () => homeBloc.homeItems[index].onPress(context), + ); + }, + ), + ), + ), + ], ), ], ), diff --git a/lib/pages/home/view/tree_page.dart b/lib/pages/home/view/tree_page.dart index 2166467f..9458e361 100644 --- a/lib/pages/home/view/tree_page.dart +++ b/lib/pages/home/view/tree_page.dart @@ -1,185 +1,185 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:graphview/GraphView.dart'; -import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; -import 'package:syncrow_web/pages/home/bloc/home_event.dart'; -import 'package:syncrow_web/pages/home/bloc/home_state.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_bloc/flutter_bloc.dart'; +// import 'package:graphview/GraphView.dart'; +// import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; +// import 'package:syncrow_web/pages/home/bloc/home_event.dart'; +// import 'package:syncrow_web/pages/home/bloc/home_state.dart'; -class TreeWidget extends StatelessWidget { - const TreeWidget({super.key}); +// class TreeWidget extends StatelessWidget { +// const TreeWidget({super.key}); - @override - Widget build(BuildContext context) { - // final HomeBloc homeBloc = BlocProvider.of(context); - String firstNodeName = ''; - String secondNodeName = ''; +// @override +// Widget build(BuildContext context) { +// // final HomeBloc homeBloc = BlocProvider.of(context); +// String firstNodeName = ''; +// String secondNodeName = ''; - return SafeArea( - child: Container( - padding: const EdgeInsets.all(24), - width: MediaQuery.sizeOf(context).width, - height: MediaQuery.sizeOf(context).height, - alignment: AlignmentDirectional.center, - child: Column( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - BlocBuilder(builder: (context, state) { - if (state is HomeInitial) { - return Wrap( - children: [ - SizedBox( - width: 100, - child: TextFormField( - decoration: const InputDecoration( - labelText: "Subtree separation"), - onChanged: (text) { - firstNodeName = text; - }, - ), - ), - const SizedBox( - width: 8, - ), - Container( - width: 100, - child: TextFormField( - decoration: InputDecoration(labelText: "Node Name"), - onChanged: (text) { - secondNodeName = text; - }, - ), - ), - ElevatedButton( - onPressed: () { - final node1 = Node.Id(firstNodeName); - final node2 = Node.Id(secondNodeName); - context.read().add(CreateNewNode( - sourceNode: node1, destinationNode: node2)); - }, - child: Text("Add"), - ) - ], - ); - } - if (state is HomeUpdateTree) { - return Expanded( - child: InteractiveViewer( - constrained: false, - boundaryMargin: const EdgeInsets.all(100), - minScale: 0.01, - maxScale: 5.6, - child: GraphView( - graph: state.graph, - algorithm: BuchheimWalkerAlgorithm( - state.builder, TreeEdgeRenderer(state.builder)), - paint: Paint() - ..color = Colors.green - ..strokeWidth = 1 - ..style = PaintingStyle.stroke, - builder: (Node node) { - // I can decide what widget should be shown here based on the id - var nodeName = node.key!.value; - return rectangleWidget(nodeName, node, context); - }, - )), - ); - } else { - return Container(); - } - }) - ], - ), - ), - ); - } -} +// return SafeArea( +// child: Container( +// padding: const EdgeInsets.all(24), +// width: MediaQuery.sizeOf(context).width, +// height: MediaQuery.sizeOf(context).height, +// alignment: AlignmentDirectional.center, +// child: Column( +// mainAxisSize: MainAxisSize.max, +// crossAxisAlignment: CrossAxisAlignment.center, +// mainAxisAlignment: MainAxisAlignment.center, +// children: [ +// BlocBuilder(builder: (context, state) { +// if (state is HomeInitial) { +// return Wrap( +// children: [ +// SizedBox( +// width: 100, +// child: TextFormField( +// decoration: const InputDecoration( +// labelText: "Subtree separation"), +// onChanged: (text) { +// firstNodeName = text; +// }, +// ), +// ), +// const SizedBox( +// width: 8, +// ), +// Container( +// width: 100, +// child: TextFormField( +// decoration: InputDecoration(labelText: "Node Name"), +// onChanged: (text) { +// secondNodeName = text; +// }, +// ), +// ), +// ElevatedButton( +// onPressed: () { +// final node1 = Node.Id(firstNodeName); +// final node2 = Node.Id(secondNodeName); +// context.read().add(CreateNewNode( +// sourceNode: node1, destinationNode: node2)); +// }, +// child: Text("Add"), +// ) +// ], +// ); +// } +// if (state is HomeUpdateTree) { +// return Expanded( +// child: InteractiveViewer( +// constrained: false, +// boundaryMargin: const EdgeInsets.all(100), +// minScale: 0.01, +// maxScale: 5.6, +// child: GraphView( +// graph: state.graph, +// algorithm: BuchheimWalkerAlgorithm( +// state.builder, TreeEdgeRenderer(state.builder)), +// paint: Paint() +// ..color = Colors.green +// ..strokeWidth = 1 +// ..style = PaintingStyle.stroke, +// builder: (Node node) { +// // I can decide what widget should be shown here based on the id +// var nodeName = node.key!.value; +// return rectangleWidget(nodeName, node, context); +// }, +// )), +// ); +// } else { +// return Container(); +// } +// }) +// ], +// ), +// ), +// ); +// } +// } -Widget rectangleWidget(String text, Node node, BuildContext blocContext) { - String nodeName = ''; - return InkWell( - onTap: () { - showDialog( - context: blocContext, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Add a child'), - content: TextField( - decoration: - const InputDecoration(hintText: 'Enter your text here'), - onChanged: (value) { - nodeName = value; - }, - ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text('Close'), - ), - TextButton( - onPressed: () { - if (nodeName.isNotEmpty) { - final newNode = Node.Id(nodeName); - blocContext.read().add(CreateNewNode( - sourceNode: node, destinationNode: newNode)); - } - Navigator.of(context).pop(); - }, - child: Text('Add'), - ), - ], - ); - }, - ); - }, - child: Container( - width: MediaQuery.of(blocContext).size.width * 0.2, - margin: EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0), - padding: EdgeInsets.all(20.0), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(10.0), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), // changes position of shadow - ), - ], - ), - child: Row( - children: [ - const SizedBox( - child: Icon( - Icons.location_on, - color: Colors.blue, - size: 40.0, - ), - ), - const SizedBox(width: 10.0), - SizedBox( - child: Text( - text, - style: const TextStyle( - fontSize: 24.0, - fontWeight: FontWeight.bold, - ), - ), - ), - const Spacer(), - Container( - child: const Icon( - Icons.add_circle_outline, - color: Colors.grey, - size: 24.0, - ), - ), - ], - ), - ), - ); -} +// Widget rectangleWidget(String text, Node node, BuildContext blocContext) { +// String nodeName = ''; +// return InkWell( +// onTap: () { +// showDialog( +// context: blocContext, +// builder: (BuildContext context) { +// return AlertDialog( +// title: const Text('Add a child'), +// content: TextField( +// decoration: +// const InputDecoration(hintText: 'Enter your text here'), +// onChanged: (value) { +// nodeName = value; +// }, +// ), +// actions: [ +// TextButton( +// onPressed: () { +// Navigator.of(context).pop(); +// }, +// child: Text('Close'), +// ), +// TextButton( +// onPressed: () { +// if (nodeName.isNotEmpty) { +// final newNode = Node.Id(nodeName); +// blocContext.read().add(CreateNewNode( +// sourceNode: node, destinationNode: newNode)); +// } +// Navigator.of(context).pop(); +// }, +// child: Text('Add'), +// ), +// ], +// ); +// }, +// ); +// }, +// child: Container( +// width: MediaQuery.of(blocContext).size.width * 0.2, +// margin: EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0), +// padding: EdgeInsets.all(20.0), +// decoration: BoxDecoration( +// color: Colors.white, +// borderRadius: BorderRadius.circular(10.0), +// boxShadow: [ +// BoxShadow( +// color: Colors.grey.withOpacity(0.5), +// spreadRadius: 2, +// blurRadius: 5, +// offset: Offset(0, 3), // changes position of shadow +// ), +// ], +// ), +// child: Row( +// children: [ +// const SizedBox( +// child: Icon( +// Icons.location_on, +// color: Colors.blue, +// size: 40.0, +// ), +// ), +// const SizedBox(width: 10.0), +// SizedBox( +// child: Text( +// text, +// style: const TextStyle( +// fontSize: 24.0, +// fontWeight: FontWeight.bold, +// ), +// ), +// ), +// const Spacer(), +// Container( +// child: const Icon( +// Icons.add_circle_outline, +// color: Colors.grey, +// size: 24.0, +// ), +// ), +// ], +// ), +// ), +// ); +// } diff --git a/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart deleted file mode 100644 index 1dd84c19..00000000 --- a/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/ac_dialog.dart'; -import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/one_gang_switch_dialog.dart'; -import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/three_gang_switch_dialog.dart'; -import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/two_gang_switch_dialog.dart'; -import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; - -class DeviceDialogHelper { - static Future?> showDeviceDialog( - BuildContext context, - Map data, { - required bool removeComparetors, - }) async { - final functions = data['functions'] as List; - - try { - final result = await _getDialogForDeviceType( - context, - data['productType'], - data, - functions, - removeComparetors: removeComparetors, - ); - - if (result != null) { - return result; - } - } catch (e) { - debugPrint('Error: $e'); - } - - return null; - } - - static Future?> _getDialogForDeviceType( - BuildContext context, - String productType, - Map data, - List functions, - {required bool removeComparetors}) async { - final routineBloc = context.read(); - final deviceSelectedFunctions = - routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? []; - - switch (productType) { - case 'AC': - return ACHelper.showACFunctionsDialog( - context, - functions, - data['device'], - deviceSelectedFunctions, - data['uniqueCustomId'], - removeComparetors); - - case '1G': - return OneGangSwitchHelper.showSwitchFunctionsDialog( - context, - functions, - data['device'], - deviceSelectedFunctions, - data['uniqueCustomId'], - removeComparetors); - case '2G': - return TwoGangSwitchHelper.showSwitchFunctionsDialog( - context, - functions, - data['device'], - deviceSelectedFunctions, - data['uniqueCustomId'], - removeComparetors); - case '3G': - return ThreeGangSwitchHelper.showSwitchFunctionsDialog( - context, - functions, - data['device'], - deviceSelectedFunctions, - data['uniqueCustomId'], - removeComparetors); - default: - return null; - } - } -} diff --git a/lib/pages/routiens/view/routines_view.dart b/lib/pages/routiens/view/routines_view.dart deleted file mode 100644 index 3dad14bf..00000000 --- a/lib/pages/routiens/view/routines_view.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart'; -import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart'; -import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/routine_view_card.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; - -class RoutinesView extends StatefulWidget { - const RoutinesView({super.key}); - - @override - State createState() => _RoutinesViewState(); -} - -class _RoutinesViewState extends State { - @override - void initState() { - super.initState(); - context.read().add(FetchDevicesInRoutine()); - } - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - if (state.createRoutineView) { - return const CreateNewRoutineView(); - } - return Padding( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Create New Routines", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox( - height: 10, - ), - RoutineViewCard( - onTap: () { - context.read().add( - (ResetRoutineState()), - ); - BlocProvider.of(context).add( - const CreateNewRoutineViewEvent(createRoutineView: true), - ); - }, - icon: Icons.add, - textString: '', - ), - const SizedBox( - height: 15, - ), - const Expanded(child: FetchRoutineScenesAutomation()), - ], - ), - ); - }, - ); - } -} diff --git a/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart b/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart deleted file mode 100644 index 62780d5f..00000000 --- a/lib/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart +++ /dev/null @@ -1,143 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/routine_view_card.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/helpers/responsice_layout_helper/responsive_layout_helper.dart'; - -class FetchRoutineScenesAutomation extends StatefulWidget { - const FetchRoutineScenesAutomation({super.key}); - - @override - State createState() => _FetchRoutineScenesState(); -} - -class _FetchRoutineScenesState extends State - with HelperResponsiveLayout { - @override - void initState() { - super.initState(); - context.read() - ..add(const LoadScenes(spaceId, communityId)) - ..add(const LoadAutomation(spaceId)); - } - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return state.isLoading - ? const Center( - child: CircularProgressIndicator(), - ) - : SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "Scenes (Tab to Run)", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 10), - if (state.scenes.isEmpty) - Text( - "No scenes found", - style: context.textTheme.bodyMedium?.copyWith( - color: ColorsManager.grayColor, - ), - ), - if (state.scenes.isNotEmpty) - ConstrainedBox( - constraints: BoxConstraints( - maxHeight: isSmallScreenSize(context) ? 160 : 170, - ), - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: state.scenes.length, - itemBuilder: (context, index) => Padding( - padding: EdgeInsets.only( - right: isSmallScreenSize(context) ? 4.0 : 8.0, - ), - child: RoutineViewCard( - onTap: () { - BlocProvider.of(context).add( - const CreateNewRoutineViewEvent(createRoutineView: true), - ); - context.read().add( - GetSceneDetails( - sceneId: state.scenes[index].id, - isTabToRun: true, - isUpdate: true, - ), - ); - }, - textString: state.scenes[index].name, - icon: state.scenes[index].icon ?? Assets.logoHorizontal, - isFromScenes: true, - iconInBytes: state.scenes[index].iconInBytes, - ), - ), - ), - ), - const SizedBox(height: 15), - Text( - "Automations", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 10), - if (state.automations.isEmpty) - Text( - "No automations found", - style: context.textTheme.bodyMedium?.copyWith( - color: ColorsManager.grayColor, - ), - ), - if (state.automations.isNotEmpty) - ConstrainedBox( - constraints: BoxConstraints( - maxHeight: isSmallScreenSize(context) ? 160 : 170, - ), - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: state.automations.length, - itemBuilder: (context, index) => Padding( - padding: EdgeInsets.only( - right: isSmallScreenSize(context) ? 4.0 : 8.0, - ), - child: RoutineViewCard( - onTap: () { - BlocProvider.of(context).add( - const CreateNewRoutineViewEvent(createRoutineView: true), - ); - context.read().add( - GetAutomationDetails( - automationId: state.automations[index].id, - isAutomation: true, - isUpdate: true), - ); - }, - textString: state.automations[index].name, - icon: state.automations[index].icon ?? Assets.automation, - ), - ), - ), - ), - ], - ), - ), - ); - }, - ); - } -} diff --git a/lib/pages/routiens/bloc/effective_period/effect_period_bloc.dart b/lib/pages/routines/bloc/effective_period/effect_period_bloc.dart similarity index 96% rename from lib/pages/routiens/bloc/effective_period/effect_period_bloc.dart rename to lib/pages/routines/bloc/effective_period/effect_period_bloc.dart index 7f4ca22e..fd56d232 100644 --- a/lib/pages/routiens/bloc/effective_period/effect_period_bloc.dart +++ b/lib/pages/routines/bloc/effective_period/effect_period_bloc.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart'; -import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart'; +import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_event.dart'; +import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_state.dart'; import 'package:syncrow_web/utils/constants/app_enum.dart'; class EffectPeriodBloc extends Bloc { diff --git a/lib/pages/routiens/bloc/effective_period/effect_period_event.dart b/lib/pages/routines/bloc/effective_period/effect_period_event.dart similarity index 95% rename from lib/pages/routiens/bloc/effective_period/effect_period_event.dart rename to lib/pages/routines/bloc/effective_period/effect_period_event.dart index 20f686d6..a7ae829b 100644 --- a/lib/pages/routiens/bloc/effective_period/effect_period_event.dart +++ b/lib/pages/routines/bloc/effective_period/effect_period_event.dart @@ -1,5 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart'; +import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart'; import 'package:syncrow_web/utils/constants/app_enum.dart'; abstract class EffectPeriodEvent extends Equatable { diff --git a/lib/pages/routiens/bloc/effective_period/effect_period_state.dart b/lib/pages/routines/bloc/effective_period/effect_period_state.dart similarity index 100% rename from lib/pages/routiens/bloc/effective_period/effect_period_state.dart rename to lib/pages/routines/bloc/effective_period/effect_period_state.dart diff --git a/lib/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart b/lib/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart similarity index 86% rename from lib/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart rename to lib/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart index 760d5697..a196ff27 100644 --- a/lib/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart +++ b/lib/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; part 'functions_bloc_event.dart'; part 'functions_bloc_state.dart'; @@ -26,8 +26,7 @@ class FunctionBloc extends Bloc { functionCode: event.functionData.functionCode, operationName: event.functionData.operationName, value: event.functionData.value ?? existingData.value, - valueDescription: event.functionData.valueDescription ?? - existingData.valueDescription, + valueDescription: event.functionData.valueDescription ?? existingData.valueDescription, condition: event.functionData.condition ?? existingData.condition, ); } else { @@ -59,10 +58,8 @@ class FunctionBloc extends Bloc { ); } - FutureOr _onSelectFunction( - SelectFunction event, Emitter emit) { + FutureOr _onSelectFunction(SelectFunction event, Emitter emit) { emit(state.copyWith( - selectedFunction: event.functionCode, - selectedOperationName: event.operationName)); + selectedFunction: event.functionCode, selectedOperationName: event.operationName)); } } diff --git a/lib/pages/routiens/bloc/functions_bloc/functions_bloc_event.dart b/lib/pages/routines/bloc/functions_bloc/functions_bloc_event.dart similarity index 100% rename from lib/pages/routiens/bloc/functions_bloc/functions_bloc_event.dart rename to lib/pages/routines/bloc/functions_bloc/functions_bloc_event.dart diff --git a/lib/pages/routiens/bloc/functions_bloc/functions_bloc_state.dart b/lib/pages/routines/bloc/functions_bloc/functions_bloc_state.dart similarity index 100% rename from lib/pages/routiens/bloc/functions_bloc/functions_bloc_state.dart rename to lib/pages/routines/bloc/functions_bloc/functions_bloc_state.dart diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart similarity index 96% rename from lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart rename to lib/pages/routines/bloc/routine_bloc/routine_bloc.dart index a90ea116..bc83c40f 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart @@ -4,12 +4,12 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; -import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart'; -import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart'; -import 'package:syncrow_web/pages/routiens/models/delay/delay_fucntions.dart'; -import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; -import 'package:syncrow_web/pages/routiens/models/routine_details_model.dart'; -import 'package:syncrow_web/pages/routiens/models/routine_model.dart'; +import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart'; +import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_scene_model.dart'; +import 'package:syncrow_web/pages/routines/models/delay/delay_fucntions.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/routine_details_model.dart'; +import 'package:syncrow_web/pages/routines/models/routine_model.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/services/routines_api.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -19,8 +19,8 @@ import 'package:uuid/uuid.dart'; part 'routine_event.dart'; part 'routine_state.dart'; -const spaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6'; -const communityId = 'aff21a57-2f91-4e5c-b99b-0182c3ab65a9'; +String spaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6'; +String communityId = 'aff21a57-2f91-4e5c-b99b-0182c3ab65a9'; class RoutineBloc extends Bloc { RoutineBloc() : super(const RoutineState()) { @@ -57,8 +57,8 @@ class RoutineBloc extends Bloc { emit(state.copyWith(routineTab: event.isRoutineTab, createRoutineView: false)); add(ResetRoutineState()); if (event.isRoutineTab) { - add(const LoadScenes(spaceId, communityId)); - add(const LoadAutomation(spaceId)); + add(LoadScenes(spaceId, communityId)); + add(LoadAutomation(spaceId)); } } @@ -156,18 +156,25 @@ class RoutineBloc extends Bloc { emit(state.copyWith(isLoading: true, errorMessage: null)); try { - final scenes = await SceneApi.getScenesByUnitId(event.unitId, event.communityId); + spaceId = event.spaceId; + communityId = event.communityId; + + List scenes = []; + + if (communityId.isNotEmpty && spaceId.isNotEmpty) { + scenes = await SceneApi.getScenes(event.spaceId, event.communityId); + } emit(state.copyWith( scenes: scenes, isLoading: false, )); } catch (e) { emit(state.copyWith( - isLoading: false, - loadScenesErrorMessage: 'Failed to load scenes', - errorMessage: '', - loadAutomationErrorMessage: '', - )); + isLoading: false, + loadScenesErrorMessage: 'Failed to load scenes', + errorMessage: '', + loadAutomationErrorMessage: '', + scenes: [])); } } @@ -175,27 +182,22 @@ class RoutineBloc extends Bloc { emit(state.copyWith(isLoading: true, errorMessage: null)); try { - final automations = await SceneApi.getAutomationByUnitId(event.unitId); - if (automations.isNotEmpty) { - emit(state.copyWith( - automations: automations, - isLoading: false, - )); - } else { - emit(state.copyWith( + spaceId = event.spaceId; + List automations = []; + if (spaceId.isNotEmpty) { + automations = await SceneApi.getAutomation(event.spaceId); + } + emit(state.copyWith( + automations: automations, + isLoading: false, + )); + } catch (e) { + emit(state.copyWith( isLoading: false, loadAutomationErrorMessage: 'Failed to load automations', errorMessage: '', loadScenesErrorMessage: '', - )); - } - } catch (e) { - emit(state.copyWith( - isLoading: false, - loadAutomationErrorMessage: 'Failed to load automations', - errorMessage: '', - loadScenesErrorMessage: '', - )); + automations: [])); } } @@ -290,8 +292,8 @@ class RoutineBloc extends Bloc { final result = await SceneApi.createScene(createSceneModel); if (result['success']) { add(ResetRoutineState()); - add(const LoadScenes(spaceId, communityId)); - add(const LoadAutomation(spaceId)); + add(LoadScenes(spaceId, communityId)); + add(LoadAutomation(spaceId)); } else { emit(state.copyWith( isLoading: false, @@ -419,8 +421,8 @@ class RoutineBloc extends Bloc { final result = await SceneApi.createAutomation(createAutomationModel); if (result['success']) { add(ResetRoutineState()); - add(const LoadAutomation(spaceId)); - add(const LoadScenes(spaceId, communityId)); + add(LoadAutomation(spaceId)); + add(LoadScenes(spaceId, communityId)); } else { emit(state.copyWith( isLoading: false, @@ -785,8 +787,8 @@ class RoutineBloc extends Bloc { SceneApi.deleteAutomation(unitUuid: spaceId, automationId: state.automationId ?? ''); } - add(const LoadScenes(spaceId, communityId)); - add(const LoadAutomation(spaceId)); + add(LoadScenes(spaceId, communityId)); + add(LoadAutomation(spaceId)); add(ResetRoutineState()); emit(state.copyWith(isLoading: false, createRoutineView: false)); } catch (e) { @@ -814,7 +816,7 @@ class RoutineBloc extends Bloc { FutureOr _fetchDevices(FetchDevicesInRoutine event, Emitter emit) async { emit(state.copyWith(isLoading: true)); try { - final devices = await DevicesManagementApi().fetchDevices(); + final devices = await DevicesManagementApi().fetchDevices(communityId, spaceId); emit(state.copyWith(isLoading: false, devices: devices)); } catch (e) { @@ -892,8 +894,8 @@ class RoutineBloc extends Bloc { final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? ''); if (result['success']) { add(ResetRoutineState()); - add(const LoadScenes(spaceId, communityId)); - add(const LoadAutomation(spaceId)); + add(LoadScenes(spaceId, communityId)); + add(LoadAutomation(spaceId)); } else { emit(state.copyWith( isLoading: false, @@ -1021,8 +1023,8 @@ class RoutineBloc extends Bloc { if (result['success']) { add(ResetRoutineState()); - add(const LoadAutomation(spaceId)); - add(const LoadScenes(spaceId, communityId)); + add(LoadAutomation(spaceId)); + add(LoadScenes(spaceId, communityId)); } else { emit(state.copyWith( isLoading: false, diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart b/lib/pages/routines/bloc/routine_bloc/routine_event.dart similarity index 95% rename from lib/pages/routiens/bloc/routine_bloc/routine_event.dart rename to lib/pages/routines/bloc/routine_bloc/routine_event.dart index f3d35eb8..e412aae6 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_event.dart @@ -27,22 +27,22 @@ class AddToThenContainer extends RoutineEvent { } class LoadScenes extends RoutineEvent { - final String unitId; + final String spaceId; final String communityId; - const LoadScenes(this.unitId, this.communityId); + const LoadScenes(this.spaceId, this.communityId); @override - List get props => [unitId, communityId]; + List get props => [spaceId, communityId]; } class LoadAutomation extends RoutineEvent { - final String unitId; + final String spaceId; - const LoadAutomation(this.unitId); + const LoadAutomation(this.spaceId); @override - List get props => [unitId]; + List get props => [spaceId]; } class AddFunctionToRoutine extends RoutineEvent { diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart b/lib/pages/routines/bloc/routine_bloc/routine_state.dart similarity index 100% rename from lib/pages/routiens/bloc/routine_bloc/routine_state.dart rename to lib/pages/routines/bloc/routine_bloc/routine_state.dart diff --git a/lib/pages/routiens/bloc/setting_bloc/setting_bloc.dart b/lib/pages/routines/bloc/setting_bloc/setting_bloc.dart similarity index 89% rename from lib/pages/routiens/bloc/setting_bloc/setting_bloc.dart rename to lib/pages/routines/bloc/setting_bloc/setting_bloc.dart index f70aed34..843b35df 100644 --- a/lib/pages/routiens/bloc/setting_bloc/setting_bloc.dart +++ b/lib/pages/routines/bloc/setting_bloc/setting_bloc.dart @@ -1,7 +1,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_event.dart'; -import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_state.dart'; -import 'package:syncrow_web/pages/routiens/models/icon_model.dart'; +import 'package:syncrow_web/pages/routines/bloc/setting_bloc/setting_event.dart'; +import 'package:syncrow_web/pages/routines/bloc/setting_bloc/setting_state.dart'; +import 'package:syncrow_web/pages/routines/models/icon_model.dart'; import 'package:syncrow_web/services/routines_api.dart'; class SettingBloc extends Bloc { diff --git a/lib/pages/routiens/bloc/setting_bloc/setting_event.dart b/lib/pages/routines/bloc/setting_bloc/setting_event.dart similarity index 100% rename from lib/pages/routiens/bloc/setting_bloc/setting_event.dart rename to lib/pages/routines/bloc/setting_bloc/setting_event.dart diff --git a/lib/pages/routiens/bloc/setting_bloc/setting_state.dart b/lib/pages/routines/bloc/setting_bloc/setting_state.dart similarity index 94% rename from lib/pages/routiens/bloc/setting_bloc/setting_state.dart rename to lib/pages/routines/bloc/setting_bloc/setting_state.dart index 7c88d67c..ae7571ce 100644 --- a/lib/pages/routiens/bloc/setting_bloc/setting_state.dart +++ b/lib/pages/routines/bloc/setting_bloc/setting_state.dart @@ -1,5 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/routiens/models/icon_model.dart'; +import 'package:syncrow_web/pages/routines/models/icon_model.dart'; abstract class SettingState extends Equatable { const SettingState(); diff --git a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart new file mode 100644 index 00000000..ed2af95a --- /dev/null +++ b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ac_dialog.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; + +class DeviceDialogHelper { + static Future?> showDeviceDialog( + BuildContext context, + Map data, { + required bool removeComparetors, + }) async { + final functions = data['functions'] as List; + + try { + final result = await _getDialogForDeviceType( + context, + data['productType'], + data, + functions, + removeComparetors: removeComparetors, + ); + + if (result != null) { + return result; + } + } catch (e) { + debugPrint('Error: $e'); + } + + return null; + } + + static Future?> _getDialogForDeviceType(BuildContext context, + String productType, Map data, List functions, + {required bool removeComparetors}) async { + final routineBloc = context.read(); + final deviceSelectedFunctions = + routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? []; + + switch (productType) { + case 'AC': + return ACHelper.showACFunctionsDialog(context, functions, data['device'], + deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors); + + case '1G': + return OneGangSwitchHelper.showSwitchFunctionsDialog(context, functions, data['device'], + deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors); + case '2G': + return TwoGangSwitchHelper.showSwitchFunctionsDialog(context, functions, data['device'], + deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors); + case '3G': + return ThreeGangSwitchHelper.showSwitchFunctionsDialog(context, functions, data['device'], + deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors); + default: + return null; + } + } +} diff --git a/lib/pages/routiens/helper/duration_format_helper.dart b/lib/pages/routines/helper/duration_format_helper.dart similarity index 100% rename from lib/pages/routiens/helper/duration_format_helper.dart rename to lib/pages/routines/helper/duration_format_helper.dart diff --git a/lib/pages/routiens/helper/save_routine_helper.dart b/lib/pages/routines/helper/save_routine_helper.dart similarity index 97% rename from lib/pages/routiens/helper/save_routine_helper.dart rename to lib/pages/routines/helper/save_routine_helper.dart index d73a69a7..e2ca5ede 100644 --- a/lib/pages/routiens/helper/save_routine_helper.dart +++ b/lib/pages/routines/helper/save_routine_helper.dart @@ -3,9 +3,9 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_footer.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'; diff --git a/lib/pages/routiens/models/ac/ac_function.dart b/lib/pages/routines/models/ac/ac_function.dart similarity index 96% rename from lib/pages/routiens/models/ac/ac_function.dart rename to lib/pages/routines/models/ac/ac_function.dart index 8feccda7..43b58394 100644 --- a/lib/pages/routiens/models/ac/ac_function.dart +++ b/lib/pages/routines/models/ac/ac_function.dart @@ -1,6 +1,6 @@ import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; -import 'package:syncrow_web/pages/routiens/models/ac/ac_operational_value.dart'; -import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/ac/ac_operational_value.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/utils/constants/app_enum.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; diff --git a/lib/pages/routiens/models/ac/ac_operational_value.dart b/lib/pages/routines/models/ac/ac_operational_value.dart similarity index 100% rename from lib/pages/routiens/models/ac/ac_operational_value.dart rename to lib/pages/routines/models/ac/ac_operational_value.dart diff --git a/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart b/lib/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart similarity index 100% rename from lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart rename to lib/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart diff --git a/lib/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart b/lib/pages/routines/models/create_scene_and_autoamtion/create_scene_model.dart similarity index 100% rename from lib/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart rename to lib/pages/routines/models/create_scene_and_autoamtion/create_scene_model.dart diff --git a/lib/pages/routiens/models/delay/delay_fucntions.dart b/lib/pages/routines/models/delay/delay_fucntions.dart similarity index 85% rename from lib/pages/routiens/models/delay/delay_fucntions.dart rename to lib/pages/routines/models/delay/delay_fucntions.dart index ff04251a..428825f4 100644 --- a/lib/pages/routiens/models/delay/delay_fucntions.dart +++ b/lib/pages/routines/models/delay/delay_fucntions.dart @@ -1,5 +1,5 @@ -import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; -import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class DelayFunction extends BaseSwitchFunction { diff --git a/lib/pages/routiens/models/device_functions.dart b/lib/pages/routines/models/device_functions.dart similarity index 100% rename from lib/pages/routiens/models/device_functions.dart rename to lib/pages/routines/models/device_functions.dart diff --git a/lib/pages/routiens/models/gang_switches/base_switch_function.dart b/lib/pages/routines/models/gang_switches/base_switch_function.dart similarity index 72% rename from lib/pages/routiens/models/gang_switches/base_switch_function.dart rename to lib/pages/routines/models/gang_switches/base_switch_function.dart index f180b203..c124a8f7 100644 --- a/lib/pages/routiens/models/gang_switches/base_switch_function.dart +++ b/lib/pages/routines/models/gang_switches/base_switch_function.dart @@ -1,5 +1,5 @@ -import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; -import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; abstract class BaseSwitchFunction extends DeviceFunction { BaseSwitchFunction({ diff --git a/lib/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart b/lib/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart similarity index 91% rename from lib/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart rename to lib/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart index 2e20e40e..9451f89f 100644 --- a/lib/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart +++ b/lib/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart @@ -1,5 +1,5 @@ -import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; -import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class OneGangSwitchFunction extends BaseSwitchFunction { diff --git a/lib/pages/routiens/models/gang_switches/switch_operational_value.dart b/lib/pages/routines/models/gang_switches/switch_operational_value.dart similarity index 100% rename from lib/pages/routiens/models/gang_switches/switch_operational_value.dart rename to lib/pages/routines/models/gang_switches/switch_operational_value.dart diff --git a/lib/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart b/lib/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart similarity index 89% rename from lib/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart rename to lib/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart index 7f4710f0..ca0ed497 100644 --- a/lib/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart +++ b/lib/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart @@ -1,5 +1,5 @@ -import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; -import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class ThreeGangSwitch1Function extends BaseSwitchFunction { @@ -26,8 +26,7 @@ class ThreeGangSwitch1Function extends BaseSwitchFunction { } class ThreeGangCountdown1Function extends BaseSwitchFunction { - ThreeGangCountdown1Function( - {required super.deviceId, required super.deviceName}) + ThreeGangCountdown1Function({required super.deviceId, required super.deviceName}) : super( code: 'countdown_1', operationName: 'Light 1 Countdown', @@ -71,8 +70,7 @@ class ThreeGangSwitch2Function extends BaseSwitchFunction { } class ThreeGangCountdown2Function extends BaseSwitchFunction { - ThreeGangCountdown2Function( - {required super.deviceId, required super.deviceName}) + ThreeGangCountdown2Function({required super.deviceId, required super.deviceName}) : super( code: 'countdown_2', operationName: 'Light 2 Countdown', @@ -116,8 +114,7 @@ class ThreeGangSwitch3Function extends BaseSwitchFunction { } class ThreeGangCountdown3Function extends BaseSwitchFunction { - ThreeGangCountdown3Function( - {required super.deviceId, required super.deviceName}) + ThreeGangCountdown3Function({required super.deviceId, required super.deviceName}) : super( code: 'countdown_3', operationName: 'Light 3 Countdown', diff --git a/lib/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart b/lib/pages/routines/models/gang_switches/two_gang_switch/two_gang_switch.dart similarity index 88% rename from lib/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart rename to lib/pages/routines/models/gang_switches/two_gang_switch/two_gang_switch.dart index 91bda15c..95de1122 100644 --- a/lib/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart +++ b/lib/pages/routines/models/gang_switches/two_gang_switch/two_gang_switch.dart @@ -1,5 +1,5 @@ -import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; -import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class TwoGangSwitch1Function extends BaseSwitchFunction { @@ -49,8 +49,7 @@ class TwoGangSwitch2Function extends BaseSwitchFunction { } class TwoGangCountdown1Function extends BaseSwitchFunction { - TwoGangCountdown1Function( - {required super.deviceId, required super.deviceName}) + TwoGangCountdown1Function({required super.deviceId, required super.deviceName}) : super( code: 'countdown_1', operationName: 'Light 1 Countdown', @@ -71,8 +70,7 @@ class TwoGangCountdown1Function extends BaseSwitchFunction { } class TwoGangCountdown2Function extends BaseSwitchFunction { - TwoGangCountdown2Function( - {required super.deviceId, required super.deviceName}) + TwoGangCountdown2Function({required super.deviceId, required super.deviceName}) : super( code: 'countdown_2', operationName: 'Light 2 Countdown', diff --git a/lib/pages/routiens/models/icon_model.dart b/lib/pages/routines/models/icon_model.dart similarity index 100% rename from lib/pages/routiens/models/icon_model.dart rename to lib/pages/routines/models/icon_model.dart diff --git a/lib/pages/routiens/models/routine_details_model.dart b/lib/pages/routines/models/routine_details_model.dart similarity index 98% rename from lib/pages/routiens/models/routine_details_model.dart rename to lib/pages/routines/models/routine_details_model.dart index 8a4a1202..c42b4d36 100644 --- a/lib/pages/routiens/models/routine_details_model.dart +++ b/lib/pages/routines/models/routine_details_model.dart @@ -1,7 +1,7 @@ import 'dart:convert'; -import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart'; -import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart'; +import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart'; +import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_scene_model.dart'; class RoutineDetailsModel { final String spaceUuid; diff --git a/lib/pages/routiens/models/routine_item.dart b/lib/pages/routines/models/routine_item.dart similarity index 100% rename from lib/pages/routiens/models/routine_item.dart rename to lib/pages/routines/models/routine_item.dart diff --git a/lib/pages/routiens/models/routine_model.dart b/lib/pages/routines/models/routine_model.dart similarity index 100% rename from lib/pages/routiens/models/routine_model.dart rename to lib/pages/routines/models/routine_model.dart diff --git a/lib/pages/routiens/view/create_new_routine_view.dart b/lib/pages/routines/view/create_new_routine_view.dart similarity index 92% rename from lib/pages/routiens/view/create_new_routine_view.dart rename to lib/pages/routines/view/create_new_routine_view.dart index dedfada8..a2d5d892 100644 --- a/lib/pages/routiens/view/create_new_routine_view.dart +++ b/lib/pages/routines/view/create_new_routine_view.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/routiens/widgets/conditions_routines_devices_view.dart'; -import 'package:syncrow_web/pages/routiens/widgets/if_container.dart'; -import 'package:syncrow_web/pages/routiens/widgets/routine_search_and_buttons.dart'; -import 'package:syncrow_web/pages/routiens/widgets/then_container.dart'; +import 'package:syncrow_web/pages/routines/widgets/conditions_routines_devices_view.dart'; +import 'package:syncrow_web/pages/routines/widgets/if_container.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_search_and_buttons.dart'; +import 'package:syncrow_web/pages/routines/widgets/then_container.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateNewRoutineView extends StatelessWidget { @@ -68,7 +68,7 @@ class CreateNewRoutineView extends StatelessWidget { width: double.infinity, color: ColorsManager.dialogBlueTitle, ), - + /// THEN Container Expanded( child: Card( diff --git a/lib/pages/routiens/view/effective_period_view.dart b/lib/pages/routines/view/effective_period_view.dart similarity index 86% rename from lib/pages/routiens/view/effective_period_view.dart rename to lib/pages/routines/view/effective_period_view.dart index 5e6c33da..b54e4075 100644 --- a/lib/pages/routiens/view/effective_period_view.dart +++ b/lib/pages/routines/view/effective_period_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/effictive_period_dialog.dart'; -import 'package:syncrow_web/pages/routiens/widgets/period_option.dart'; -import 'package:syncrow_web/pages/routiens/widgets/repeat_days.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/effictive_period_dialog.dart'; +import 'package:syncrow_web/pages/routines/widgets/period_option.dart'; +import 'package:syncrow_web/pages/routines/widgets/repeat_days.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class EffectivePeriodView extends StatelessWidget { diff --git a/lib/pages/routines/view/routines_view.dart b/lib/pages/routines/view/routines_view.dart new file mode 100644 index 00000000..f1befb05 --- /dev/null +++ b/lib/pages/routines/view/routines_view.dart @@ -0,0 +1,95 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart'; +import 'package:syncrow_web/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart'; +import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_view_card.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; +import 'package:syncrow_web/pages/space_tree/view/side_spaces_view.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/snack_bar.dart'; + +class RoutinesView extends StatefulWidget { + const RoutinesView({super.key}); + + @override + State createState() => _RoutinesViewState(); +} + +class _RoutinesViewState extends State { + @override + void initState() { + super.initState(); + context.read().add(FetchDevicesInRoutine()); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state.createRoutineView) { + return const CreateNewRoutineView(); + } + return Row( + children: [ + Expanded(child: SideSpacesView( + onSelectAction: (String communityId, String spaceId) { + context.read() + ..add(LoadScenes(spaceId, communityId)) + ..add(LoadAutomation(spaceId)); + }, + )), + Expanded( + flex: 3, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Create New Routines", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox( + height: 10, + ), + RoutineViewCard( + onTap: () { + if (context.read().selectedCommunityId.isNotEmpty && + context.read().selectedSpaceId.isNotEmpty) { + context.read().add( + (ResetRoutineState()), + ); + BlocProvider.of(context).add( + const CreateNewRoutineViewEvent(createRoutineView: true), + ); + } else { + CustomSnackBar.redSnackBar('Please select a space'); + } + }, + icon: Icons.add, + textString: '', + ), + const SizedBox( + height: 15, + ), + const Expanded(child: FetchRoutineScenesAutomation()), + ], + ), + ], + ), + ), + ), + ], + ); + }, + ); + } +} diff --git a/lib/pages/routiens/widgets/conditions_routines_devices_view.dart b/lib/pages/routines/widgets/conditions_routines_devices_view.dart similarity index 91% rename from lib/pages/routiens/widgets/conditions_routines_devices_view.dart rename to lib/pages/routines/widgets/conditions_routines_devices_view.dart index 5cc31bf3..3def44de 100644 --- a/lib/pages/routiens/widgets/conditions_routines_devices_view.dart +++ b/lib/pages/routines/widgets/conditions_routines_devices_view.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; -import 'package:syncrow_web/pages/routiens/widgets/routine_devices.dart'; -import 'package:syncrow_web/pages/routiens/widgets/routines_title_widget.dart'; -import 'package:syncrow_web/pages/routiens/widgets/scenes_and_automations.dart'; -import 'package:syncrow_web/pages/routiens/widgets/search_bar_condition_title.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/widgets/dragable_card.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_devices.dart'; +import 'package:syncrow_web/pages/routines/widgets/routines_title_widget.dart'; +import 'package:syncrow_web/pages/routines/widgets/scenes_and_automations.dart'; +import 'package:syncrow_web/pages/routines/widgets/search_bar_condition_title.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class ConditionsRoutinesDevicesView extends StatelessWidget { diff --git a/lib/pages/routiens/widgets/delete_scene.dart b/lib/pages/routines/widgets/delete_scene.dart similarity index 97% rename from lib/pages/routiens/widgets/delete_scene.dart rename to lib/pages/routines/widgets/delete_scene.dart index d829696c..10eeb493 100644 --- a/lib/pages/routiens/widgets/delete_scene.dart +++ b/lib/pages/routines/widgets/delete_scene.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/custom_dialog.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class DeleteSceneWidget extends StatelessWidget { diff --git a/lib/pages/routiens/widgets/dialog_footer.dart b/lib/pages/routines/widgets/dialog_footer.dart similarity index 100% rename from lib/pages/routiens/widgets/dialog_footer.dart rename to lib/pages/routines/widgets/dialog_footer.dart diff --git a/lib/pages/routiens/widgets/dialog_header.dart b/lib/pages/routines/widgets/dialog_header.dart similarity index 100% rename from lib/pages/routiens/widgets/dialog_header.dart rename to lib/pages/routines/widgets/dialog_header.dart diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routines/widgets/dragable_card.dart similarity index 98% rename from lib/pages/routiens/widgets/dragable_card.dart rename to lib/pages/routines/widgets/dragable_card.dart index 56961892..bc6dae82 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routines/widgets/dragable_card.dart @@ -3,8 +3,8 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; diff --git a/lib/pages/routiens/widgets/if_container.dart b/lib/pages/routines/widgets/if_container.dart similarity index 68% rename from lib/pages/routiens/widgets/if_container.dart rename to lib/pages/routines/widgets/if_container.dart index 9c357a17..d1736240 100644 --- a/lib/pages/routiens/widgets/if_container.dart +++ b/lib/pages/routines/widgets/if_container.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/helper/dialog_helper/device_dialog_helper.dart'; +import 'package:syncrow_web/pages/routines/widgets/dragable_card.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'; @@ -26,9 +26,7 @@ class IfContainer extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text('IF', - style: TextStyle( - fontSize: 18, fontWeight: FontWeight.bold)), + const Text('IF', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), if (state.isAutomation && state.ifItems.isNotEmpty) AutomationOperatorSelector( selectedOperator: state.selectedAutomationOperator), @@ -55,44 +53,34 @@ class IfContainer extends StatelessWidget { (index) => GestureDetector( onTap: () async { if (!state.isTabToRun) { - final result = await DeviceDialogHelper - .showDeviceDialog( - context, state.ifItems[index], - removeComparetors: false); + final result = await DeviceDialogHelper.showDeviceDialog( + context, state.ifItems[index], + removeComparetors: false); if (result != null) { - context.read().add( - AddToIfContainer( - state.ifItems[index], false)); - } else if (![ - 'AC', - '1G', - '2G', - '3G' - ].contains( - state.ifItems[index]['productType'])) { - context.read().add( - AddToIfContainer( - state.ifItems[index], false)); + context + .read() + .add(AddToIfContainer(state.ifItems[index], false)); + } else if (!['AC', '1G', '2G', '3G'] + .contains(state.ifItems[index]['productType'])) { + context + .read() + .add(AddToIfContainer(state.ifItems[index], false)); } } }, child: DraggableCard( - imagePath: - state.ifItems[index]['imagePath'] ?? '', + imagePath: state.ifItems[index]['imagePath'] ?? '', title: state.ifItems[index]['title'] ?? '', deviceData: state.ifItems[index], - padding: const EdgeInsets.symmetric( - horizontal: 4, vertical: 8), + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), isFromThen: false, isFromIf: true, onRemove: () { - context.read().add( - RemoveDragCard( - index: index, - isFromThen: false, - key: state.ifItems[index] - ['uniqueCustomId'])); + context.read().add(RemoveDragCard( + index: index, + isFromThen: false, + key: state.ifItems[index]['uniqueCustomId'])); }, ), )), @@ -113,23 +101,15 @@ class IfContainer extends StatelessWidget { if (!state.isTabToRun) { if (mutableData['deviceId'] == 'tab_to_run') { - context - .read() - .add(AddToIfContainer(mutableData, true)); + context.read().add(AddToIfContainer(mutableData, true)); } else { - final result = await DeviceDialogHelper.showDeviceDialog( - context, mutableData, + final result = await DeviceDialogHelper.showDeviceDialog(context, mutableData, removeComparetors: false); if (result != null) { - context - .read() - .add(AddToIfContainer(mutableData, false)); - } else if (!['AC', '1G', '2G', '3G'] - .contains(mutableData['productType'])) { - context - .read() - .add(AddToIfContainer(mutableData, false)); + context.read().add(AddToIfContainer(mutableData, false)); + } else if (!['AC', '1G', '2G', '3G'].contains(mutableData['productType'])) { + context.read().add(AddToIfContainer(mutableData, false)); } } } @@ -175,9 +155,7 @@ class AutomationOperatorSelector extends StatelessWidget { ), ), onPressed: () { - context - .read() - .add(const ChangeAutomationOperator(operator: 'or')); + context.read().add(const ChangeAutomationOperator(operator: 'or')); }, ), Container( @@ -203,9 +181,7 @@ class AutomationOperatorSelector extends StatelessWidget { ), ), onPressed: () { - context - .read() - .add(const ChangeAutomationOperator(operator: 'and')); + context.read().add(const ChangeAutomationOperator(operator: 'and')); }, ), ], diff --git a/lib/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart b/lib/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart new file mode 100644 index 00000000..ed2a9405 --- /dev/null +++ b/lib/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart @@ -0,0 +1,143 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_view_card.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.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/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class FetchRoutineScenesAutomation extends StatefulWidget { + const FetchRoutineScenesAutomation({super.key}); + + @override + State createState() => _FetchRoutineScenesState(); +} + +class _FetchRoutineScenesState extends State + with HelperResponsiveLayout { + @override + void initState() { + super.initState(); + context.read() + ..add(LoadScenes(context.read().selectedSpaceId, + context.read().selectedCommunityId)) + ..add(LoadAutomation(context.read().selectedSpaceId)); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return state.isLoading + ? const Center( + child: CircularProgressIndicator(), + ) + : Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Scenes (Tab to Run)", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + if (state.scenes.isEmpty) + Text( + "No scenes found", + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.grayColor, + ), + ), + if (state.scenes.isNotEmpty) + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: isSmallScreenSize(context) ? 160 : 170, + maxWidth: MediaQuery.sizeOf(context).width * 0.7), + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: state.scenes.length, + itemBuilder: (context, index) => Padding( + padding: EdgeInsets.only( + right: isSmallScreenSize(context) ? 4.0 : 8.0, + ), + child: RoutineViewCard( + onTap: () { + BlocProvider.of(context).add( + const CreateNewRoutineViewEvent(createRoutineView: true), + ); + context.read().add( + GetSceneDetails( + sceneId: state.scenes[index].id, + isTabToRun: true, + isUpdate: true, + ), + ); + }, + textString: state.scenes[index].name, + icon: state.scenes[index].icon ?? Assets.logoHorizontal, + isFromScenes: true, + iconInBytes: state.scenes[index].iconInBytes, + ), + ), + ), + ), + const SizedBox(height: 15), + Text( + "Automations", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + if (state.automations.isEmpty) + Text( + "No automations found", + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.grayColor, + ), + ), + if (state.automations.isNotEmpty) + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: isSmallScreenSize(context) ? 160 : 170, + maxWidth: MediaQuery.sizeOf(context).width * 0.7), + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: state.automations.length, + itemBuilder: (context, index) => Padding( + padding: EdgeInsets.only( + right: isSmallScreenSize(context) ? 4.0 : 8.0, + ), + child: RoutineViewCard( + onTap: () { + BlocProvider.of(context).add( + const CreateNewRoutineViewEvent(createRoutineView: true), + ); + context.read().add( + GetAutomationDetails( + automationId: state.automations[index].id, + isAutomation: true, + isUpdate: true), + ); + }, + textString: state.automations[index].name, + icon: state.automations[index].icon ?? Assets.automation, + ), + ), + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/pages/routiens/widgets/main_routine_view/routine_view_card.dart b/lib/pages/routines/widgets/main_routine_view/routine_view_card.dart similarity index 96% rename from lib/pages/routiens/widgets/main_routine_view/routine_view_card.dart rename to lib/pages/routines/widgets/main_routine_view/routine_view_card.dart index 7e446507..f8a2e358 100644 --- a/lib/pages/routiens/widgets/main_routine_view/routine_view_card.dart +++ b/lib/pages/routines/widgets/main_routine_view/routine_view_card.dart @@ -70,15 +70,13 @@ class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout { height: iconSize, width: iconSize, child: (isFromScenes ?? false) - ? (iconInBytes != null && - iconInBytes?.isNotEmpty == true) + ? (iconInBytes != null && iconInBytes?.isNotEmpty == true) ? Image.memory( iconInBytes!, height: iconSize, width: iconSize, fit: BoxFit.contain, - errorBuilder: (context, error, stackTrace) => - Image.asset( + errorBuilder: (context, error, stackTrace) => Image.asset( Assets.logo, height: iconSize, width: iconSize, diff --git a/lib/pages/routiens/widgets/period_option.dart b/lib/pages/routines/widgets/period_option.dart similarity index 93% rename from lib/pages/routiens/widgets/period_option.dart rename to lib/pages/routines/widgets/period_option.dart index 1871ebda..09ec590e 100644 --- a/lib/pages/routiens/widgets/period_option.dart +++ b/lib/pages/routines/widgets/period_option.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart'; -import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart'; -import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/effictive_period_dialog.dart'; +import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_event.dart'; +import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_state.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/effictive_period_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/app_enum.dart'; diff --git a/lib/pages/routiens/widgets/repeat_days.dart b/lib/pages/routines/widgets/repeat_days.dart similarity index 94% rename from lib/pages/routiens/widgets/repeat_days.dart rename to lib/pages/routines/widgets/repeat_days.dart index 8ee92367..4920408a 100644 --- a/lib/pages/routiens/widgets/repeat_days.dart +++ b/lib/pages/routines/widgets/repeat_days.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart'; -import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart'; +import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_event.dart'; +import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_state.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class RepeatDays extends StatelessWidget { diff --git a/lib/pages/routiens/widgets/routine_devices.dart b/lib/pages/routines/widgets/routine_devices.dart similarity index 90% rename from lib/pages/routiens/widgets/routine_devices.dart rename to lib/pages/routines/widgets/routine_devices.dart index 63386b72..3515cc70 100644 --- a/lib/pages/routiens/widgets/routine_devices.dart +++ b/lib/pages/routines/widgets/routine_devices.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/widgets/dragable_card.dart'; class RoutineDevices extends StatelessWidget { const RoutineDevices({super.key}); @@ -35,9 +35,7 @@ class RoutineDevices extends StatelessWidget { children: deviceList.asMap().entries.map((entry) { final device = entry.value; if (state.searchText != null && state.searchText!.isNotEmpty) { - return device.name! - .toLowerCase() - .contains(state.searchText!.toLowerCase()) + return device.name!.toLowerCase().contains(state.searchText!.toLowerCase()) ? DraggableCard( imagePath: device.getDefaultIcon(device.productType), title: device.name ?? '', diff --git a/lib/pages/routiens/widgets/routine_dialogs/ac_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart similarity index 96% rename from lib/pages/routiens/widgets/routine_dialogs/ac_dialog.dart rename to lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart index 7a630448..5f28e539 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/ac_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart'; -import 'package:syncrow_web/pages/routiens/models/ac/ac_operational_value.dart'; -import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart'; +import 'package:syncrow_web/pages/routines/models/ac/ac_operational_value.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; class ACHelper { static Future?> showACFunctionsDialog( diff --git a/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/automation_dialog.dart similarity index 87% rename from lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart rename to lib/pages/routines/widgets/routine_dialogs/automation_dialog.dart index 84ed8fdd..3c1eb1c2 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/automation_dialog.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class AutomationDialog extends StatefulWidget { @@ -31,10 +31,8 @@ class _AutomationDialogState extends State { @override void initState() { super.initState(); - List? functions = context - .read() - .state - .selectedFunctions[widget.uniqueCustomId]; + List? functions = + context.read().state.selectedFunctions[widget.uniqueCustomId]; for (DeviceFunctionData data in functions ?? []) { if (data.entityId == widget.automationId) { selectedAutomationActionExecutor = data.value; @@ -67,8 +65,7 @@ class _AutomationDialogState extends State { }), ), ListTile( - leading: - SvgPicture.asset(Assets.acPowerOff, width: 24, height: 24), + leading: SvgPicture.asset(Assets.acPowerOff, width: 24, height: 24), title: const Text('Disable'), trailing: Radio( value: 'rule_disable', diff --git a/lib/pages/routiens/widgets/routine_dialogs/delay_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/delay_dialog.dart similarity index 91% rename from lib/pages/routiens/widgets/routine_dialogs/delay_dialog.dart rename to lib/pages/routines/widgets/routine_dialogs/delay_dialog.dart index 6195a931..4580f6e1 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/delay_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/delay_dialog.dart @@ -1,10 +1,10 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; class DelayHelper { static Future?> showDelayPickerDialog( diff --git a/lib/pages/routiens/widgets/routine_dialogs/discard_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/discard_dialog.dart similarity index 97% rename from lib/pages/routiens/widgets/routine_dialogs/discard_dialog.dart rename to lib/pages/routines/widgets/routine_dialogs/discard_dialog.dart index 7897faf6..40036d32 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/discard_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/discard_dialog.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; diff --git a/lib/pages/routiens/widgets/routine_dialogs/effictive_period_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/effictive_period_dialog.dart similarity index 97% rename from lib/pages/routiens/widgets/routine_dialogs/effictive_period_dialog.dart rename to lib/pages/routines/widgets/routine_dialogs/effictive_period_dialog.dart index 70a6776d..5fc31a96 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/effictive_period_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/effictive_period_dialog.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart'; +import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_event.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/app_enum.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; diff --git a/lib/pages/routiens/widgets/routine_dialogs/one_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart similarity index 86% rename from lib/pages/routiens/widgets/routine_dialogs/one_gang_switch_dialog.dart rename to lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart index e7d7209f..adccb33d 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/one_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; -import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/helper/duration_format_helper.dart'; -import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; -import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; -import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -22,23 +22,21 @@ class OneGangSwitchHelper { String uniqueCustomId, bool removeComparetors, ) async { - List acFunctions = - functions.whereType().toList(); + List acFunctions = functions.whereType().toList(); return showDialog?>( context: context, builder: (BuildContext context) { return BlocProvider( - create: (_) => FunctionBloc() - ..add(InitializeFunctions(deviceSelectedFunctions ?? [])), + create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])), child: AlertDialog( contentPadding: EdgeInsets.zero, content: BlocBuilder( builder: (context, state) { final selectedFunction = state.selectedFunction; final selectedOperationName = state.selectedOperationName; - final selectedFunctionData = state.addedFunctions - .firstWhere((f) => f.functionCode == selectedFunction, + final selectedFunctionData = + state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction, orElse: () => DeviceFunctionData( entityId: '', functionCode: selectedFunction ?? '', @@ -85,12 +83,9 @@ class OneGangSwitchHelper { color: ColorsManager.textGray, ), onTap: () { - context - .read() - .add(SelectFunction( + context.read().add(SelectFunction( functionCode: function.code, - operationName: - function.operationName, + operationName: function.operationName, )); }, ); @@ -180,8 +175,7 @@ class OneGangSwitchHelper { ); } - final selectedFn = - acFunctions.firstWhere((f) => f.code == selectedFunction); + final selectedFn = acFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); return _buildOperationalValuesList( @@ -218,11 +212,11 @@ class OneGangSwitchHelper { selectedFunctionData, ), const SizedBox(height: 20), - _buildCountDownDisplay(context, initialValue, device, operationName, - selectedFunctionData, selectCode), + _buildCountDownDisplay( + context, initialValue, device, operationName, selectedFunctionData, selectCode), const SizedBox(height: 20), - _buildCountDownSlider(context, initialValue, device, operationName, - selectedFunctionData, selectCode), + _buildCountDownSlider( + context, initialValue, device, operationName, selectedFunctionData, selectCode), ], ); } @@ -263,8 +257,7 @@ class OneGangSwitchHelper { minHeight: 40.0, minWidth: 40.0, ), - isSelected: - conditions.map((c) => c == (currentCondition ?? "==")).toList(), + isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), children: conditions.map((c) => Text(c)).toList(), ); } @@ -312,8 +305,7 @@ class OneGangSwitchHelper { value: (initialValue ?? 0).toDouble(), min: operationalValues.minValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0, - divisions: (((operationalValues.maxValue ?? 0) - - (operationalValues.minValue ?? 0)) / + divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) / (operationalValues.stepValue ?? 1)) .round(), onChanged: (value) { @@ -365,13 +357,9 @@ class OneGangSwitchHelper { style: context.textTheme.bodyMedium, ), trailing: Icon( - isSelected - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, + isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, size: 24, - color: isSelected - ? ColorsManager.primaryColorWithOpacity - : ColorsManager.textGray, + color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray, ), onTap: () { if (!isSelected) { @@ -383,8 +371,7 @@ class OneGangSwitchHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: - selectedFunctionData?.valueDescription, + valueDescription: selectedFunctionData?.valueDescription, ), ), ); diff --git a/lib/pages/routiens/widgets/routine_dialogs/setting_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/setting_dialog.dart similarity index 97% rename from lib/pages/routiens/widgets/routine_dialogs/setting_dialog.dart rename to lib/pages/routines/widgets/routine_dialogs/setting_dialog.dart index aa4a407b..b8449838 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/setting_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/setting_dialog.dart @@ -1,17 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart'; -import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_event.dart'; -import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_state.dart'; -import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart'; -import 'package:syncrow_web/pages/routiens/models/icon_model.dart'; -import 'package:syncrow_web/pages/routiens/view/effective_period_view.dart'; -import 'package:syncrow_web/pages/routiens/widgets/delete_scene.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_event.dart'; +import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_state.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/setting_bloc/setting_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/setting_bloc/setting_event.dart'; +import 'package:syncrow_web/pages/routines/bloc/setting_bloc/setting_state.dart'; +import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart'; +import 'package:syncrow_web/pages/routines/models/icon_model.dart'; +import 'package:syncrow_web/pages/routines/view/effective_period_view.dart'; +import 'package:syncrow_web/pages/routines/widgets/delete_scene.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:flutter/cupertino.dart'; diff --git a/lib/pages/routiens/widgets/routine_dialogs/three_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart similarity index 86% rename from lib/pages/routiens/widgets/routine_dialogs/three_gang_switch_dialog.dart rename to lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart index bb23ece4..f565f07d 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/three_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; -import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/helper/duration_format_helper.dart'; -import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; -import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; -import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -22,23 +22,21 @@ class ThreeGangSwitchHelper { String uniqueCustomId, bool removeComparetors, ) async { - List switchFunctions = - functions.whereType().toList(); + List switchFunctions = functions.whereType().toList(); return showDialog?>( context: context, builder: (BuildContext context) { return BlocProvider( - create: (_) => FunctionBloc() - ..add(InitializeFunctions(deviceSelectedFunctions ?? [])), + create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])), child: AlertDialog( contentPadding: EdgeInsets.zero, content: BlocBuilder( builder: (context, state) { final selectedFunction = state.selectedFunction; final selectedOperationName = state.selectedOperationName; - final selectedFunctionData = state.addedFunctions - .firstWhere((f) => f.functionCode == selectedFunction, + final selectedFunctionData = + state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction, orElse: () => DeviceFunctionData( entityId: '', functionCode: selectedFunction ?? '', @@ -85,12 +83,9 @@ class ThreeGangSwitchHelper { color: ColorsManager.textGray, ), onTap: () { - context - .read() - .add(SelectFunction( + context.read().add(SelectFunction( functionCode: function.code, - operationName: - function.operationName, + operationName: function.operationName, )); }, ); @@ -182,8 +177,7 @@ class ThreeGangSwitchHelper { ); } - final selectedFn = - switchFunctions.firstWhere((f) => f.code == selectedFunction); + final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); return _buildOperationalValuesList( @@ -220,11 +214,11 @@ class ThreeGangSwitchHelper { selectedFunctionData, ), const SizedBox(height: 20), - _buildCountDownDisplay(context, initialValue, device, operationName, - selectedFunctionData, selectCode), + _buildCountDownDisplay( + context, initialValue, device, operationName, selectedFunctionData, selectCode), const SizedBox(height: 20), - _buildCountDownSlider(context, initialValue, device, operationName, - selectedFunctionData, selectCode), + _buildCountDownSlider( + context, initialValue, device, operationName, selectedFunctionData, selectCode), ], ); } @@ -265,8 +259,7 @@ class ThreeGangSwitchHelper { minHeight: 40.0, minWidth: 40.0, ), - isSelected: - conditions.map((c) => c == (currentCondition ?? "==")).toList(), + isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), children: conditions.map((c) => Text(c)).toList(), ); } @@ -314,8 +307,7 @@ class ThreeGangSwitchHelper { value: (initialValue ?? 0).toDouble(), min: operationalValues.minValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0, - divisions: (((operationalValues.maxValue ?? 0) - - (operationalValues.minValue ?? 0)) / + divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) / (operationalValues.stepValue ?? 1)) .round(), onChanged: (value) { @@ -367,13 +359,9 @@ class ThreeGangSwitchHelper { style: context.textTheme.bodyMedium, ), trailing: Icon( - isSelected - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, + isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, size: 24, - color: isSelected - ? ColorsManager.primaryColorWithOpacity - : ColorsManager.textGray, + color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray, ), onTap: () { if (!isSelected) { @@ -385,8 +373,7 @@ class ThreeGangSwitchHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: - selectedFunctionData?.valueDescription, + valueDescription: selectedFunctionData?.valueDescription, ), ), ); diff --git a/lib/pages/routiens/widgets/routine_dialogs/two_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart similarity index 86% rename from lib/pages/routiens/widgets/routine_dialogs/two_gang_switch_dialog.dart rename to lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart index f2a0ebd0..c894f65c 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/two_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; -import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/helper/duration_format_helper.dart'; -import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; -import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; -import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -22,23 +22,21 @@ class TwoGangSwitchHelper { String uniqueCustomId, bool removeComparetors, ) async { - List switchFunctions = - functions.whereType().toList(); + List switchFunctions = functions.whereType().toList(); return showDialog?>( context: context, builder: (BuildContext context) { return BlocProvider( - create: (_) => FunctionBloc() - ..add(InitializeFunctions(deviceSelectedFunctions ?? [])), + create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])), child: AlertDialog( contentPadding: EdgeInsets.zero, content: BlocBuilder( builder: (context, state) { final selectedFunction = state.selectedFunction; final selectedOperationName = state.selectedOperationName; - final selectedFunctionData = state.addedFunctions - .firstWhere((f) => f.functionCode == selectedFunction, + final selectedFunctionData = + state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction, orElse: () => DeviceFunctionData( entityId: '', functionCode: selectedFunction ?? '', @@ -85,12 +83,9 @@ class TwoGangSwitchHelper { color: ColorsManager.textGray, ), onTap: () { - context - .read() - .add(SelectFunction( + context.read().add(SelectFunction( functionCode: function.code, - operationName: - function.operationName, + operationName: function.operationName, )); }, ); @@ -166,8 +161,7 @@ class TwoGangSwitchHelper { required String operationName, required bool removeComparetors, }) { - if (selectedFunction == 'countdown_1' || - selectedFunction == 'countdown_2') { + if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2') { final initialValue = selectedFunctionData?.value ?? 200; return _buildTemperatureSelector( context: context, @@ -181,8 +175,7 @@ class TwoGangSwitchHelper { ); } - final selectedFn = - switchFunctions.firstWhere((f) => f.code == selectedFunction); + final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); return _buildOperationalValuesList( @@ -219,11 +212,11 @@ class TwoGangSwitchHelper { selectedFunctionData, ), const SizedBox(height: 20), - _buildCountDownDisplay(context, initialValue, device, operationName, - selectedFunctionData, selectCode), + _buildCountDownDisplay( + context, initialValue, device, operationName, selectedFunctionData, selectCode), const SizedBox(height: 20), - _buildCountDownSlider(context, initialValue, device, operationName, - selectedFunctionData, selectCode), + _buildCountDownSlider( + context, initialValue, device, operationName, selectedFunctionData, selectCode), ], ); } @@ -264,8 +257,7 @@ class TwoGangSwitchHelper { minHeight: 40.0, minWidth: 40.0, ), - isSelected: - conditions.map((c) => c == (currentCondition ?? "==")).toList(), + isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), children: conditions.map((c) => Text(c)).toList(), ); } @@ -313,8 +305,7 @@ class TwoGangSwitchHelper { value: (initialValue ?? 0).toDouble(), min: operationalValues.minValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0, - divisions: (((operationalValues.maxValue ?? 0) - - (operationalValues.minValue ?? 0)) / + divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) / (operationalValues.stepValue ?? 1)) .round(), onChanged: (value) { @@ -366,13 +357,9 @@ class TwoGangSwitchHelper { style: context.textTheme.bodyMedium, ), trailing: Icon( - isSelected - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, + isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, size: 24, - color: isSelected - ? ColorsManager.primaryColorWithOpacity - : ColorsManager.textGray, + color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray, ), onTap: () { if (!isSelected) { @@ -384,8 +371,7 @@ class TwoGangSwitchHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: - selectedFunctionData?.valueDescription, + valueDescription: selectedFunctionData?.valueDescription, ), ), ); diff --git a/lib/pages/routiens/widgets/routine_search_and_buttons.dart b/lib/pages/routines/widgets/routine_search_and_buttons.dart similarity index 98% rename from lib/pages/routiens/widgets/routine_search_and_buttons.dart rename to lib/pages/routines/widgets/routine_search_and_buttons.dart index f10694b6..efeedf9d 100644 --- a/lib/pages/routiens/widgets/routine_search_and_buttons.dart +++ b/lib/pages/routines/widgets/routine_search_and_buttons.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/helper/save_routine_helper.dart'; -import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/discard_dialog.dart'; -import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/setting_dialog.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/helper/save_routine_helper.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/discard_dialog.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/setting_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; diff --git a/lib/pages/routiens/widgets/routines_title_widget.dart b/lib/pages/routines/widgets/routines_title_widget.dart similarity index 100% rename from lib/pages/routiens/widgets/routines_title_widget.dart rename to lib/pages/routines/widgets/routines_title_widget.dart diff --git a/lib/pages/routiens/widgets/scenes_and_automations.dart b/lib/pages/routines/widgets/scenes_and_automations.dart similarity index 80% rename from lib/pages/routiens/widgets/scenes_and_automations.dart rename to lib/pages/routines/widgets/scenes_and_automations.dart index a0bd1ed6..2e268b1c 100644 --- a/lib/pages/routiens/widgets/scenes_and_automations.dart +++ b/lib/pages/routines/widgets/scenes_and_automations.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/widgets/dragable_card.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class ScenesAndAutomations extends StatefulWidget { @@ -18,8 +19,9 @@ class _ScenesAndAutomationsState extends State { void initState() { super.initState(); context.read() - ..add(const LoadScenes(spaceId, communityId)) - ..add(const LoadAutomation(spaceId)); + ..add(LoadScenes(context.read().selectedSpaceId, + context.read().selectedCommunityId)) + ..add(LoadAutomation(context.read().selectedSpaceId)); } @override @@ -34,9 +36,7 @@ class _ScenesAndAutomationsState extends State { children: scenes.asMap().entries.map((entry) { final scene = entry.value; if (state.searchText != null && state.searchText!.isNotEmpty) { - return scene.name - .toLowerCase() - .contains(state.searchText!.toLowerCase()) + return scene.name.toLowerCase().contains(state.searchText!.toLowerCase()) ? DraggableCard( imagePath: scene.icon ?? Assets.loginLogo, title: scene.name, diff --git a/lib/pages/routiens/widgets/search_bar_condition_title.dart b/lib/pages/routines/widgets/search_bar_condition_title.dart similarity index 95% rename from lib/pages/routiens/widgets/search_bar_condition_title.dart rename to lib/pages/routines/widgets/search_bar_condition_title.dart index c009f040..9ffcbf63 100644 --- a/lib/pages/routiens/widgets/search_bar_condition_title.dart +++ b/lib/pages/routines/widgets/search_bar_condition_title.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/widgets/routines_title_widget.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/widgets/routines_title_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routines/widgets/then_container.dart similarity index 96% rename from lib/pages/routiens/widgets/then_container.dart rename to lib/pages/routines/widgets/then_container.dart index 4746561a..2fcc62b8 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routines/widgets/then_container.dart @@ -2,11 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/automation_dialog.dart'; -import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/delay_dialog.dart'; -import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart'; -import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/automation_dialog.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/delay_dialog.dart'; +import 'package:syncrow_web/pages/routines/helper/dialog_helper/device_dialog_helper.dart'; +import 'package:syncrow_web/pages/routines/widgets/dragable_card.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:uuid/uuid.dart'; diff --git a/lib/pages/space_tree/bloc/space_tree_bloc.dart b/lib/pages/space_tree/bloc/space_tree_bloc.dart new file mode 100644 index 00000000..41b0d6b0 --- /dev/null +++ b/lib/pages/space_tree/bloc/space_tree_bloc.dart @@ -0,0 +1,69 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.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/services/space_mana_api.dart'; + +class SpaceTreeBloc extends Bloc { + String selectedCommunityId = ''; + String selectedSpaceId = ''; + SpaceTreeBloc() : super(const SpaceTreeState()) { + on(_fetchSpaces); + on(_onSelectSpace); + } + + _fetchSpaces(InitialEvent event, Emitter emit) async { + emit(SpaceTreeLoadingState()); + try { + // _onloadProducts(); + List communities = await CommunitySpaceManagementApi().fetchCommunities(); + + List updatedCommunities = await Future.wait( + communities.map((community) async { + List spaces = + await CommunitySpaceManagementApi().getSpaceHierarchy(community.uuid); + return CommunityModel( + uuid: community.uuid, + createdAt: community.createdAt, + updatedAt: community.updatedAt, + name: community.name, + description: community.description, + spaces: spaces, + region: community.region, + ); + }).toList(), + ); + + if (updatedCommunities.isNotEmpty && + state.selectedSpace.isEmpty && + state.selectedCommunity.isEmpty) { + selectedCommunityId = updatedCommunities[0].uuid; + } else { + selectedCommunityId = state.selectedCommunity; + selectedSpaceId = state.selectedSpace; + } + + emit(state.copyWith( + communitiesList: updatedCommunities, + selectedCommunity: selectedCommunityId, + selectedSpace: selectedSpaceId)); + } catch (e) { + emit(SpaceTreeErrorState('Error loading communities and spaces: $e')); + } + } + + _onSelectSpace(OnSelectSpaceEvent event, Emitter emit) async { + try { + selectedCommunityId = event.communityId; + selectedSpaceId = event.spaceId; + + emit(state.copyWith( + communitiesList: state.spacesList, + selectedCommunity: event.communityId, + selectedSpace: event.spaceId)); + } catch (e) { + emit(const SpaceTreeErrorState('Something went wrong')); + } + } +} diff --git a/lib/pages/space_tree/bloc/space_tree_event.dart b/lib/pages/space_tree/bloc/space_tree_event.dart new file mode 100644 index 00000000..d6c95579 --- /dev/null +++ b/lib/pages/space_tree/bloc/space_tree_event.dart @@ -0,0 +1,29 @@ +import 'package:equatable/equatable.dart'; + +class SpaceTreeEvent extends Equatable { + const SpaceTreeEvent(); + + @override + List get props => []; +} + +class InitialEvent extends SpaceTreeEvent {} + +class OnSelectSpaceEvent extends SpaceTreeEvent { + final String communityId; + final String spaceId; + + const OnSelectSpaceEvent(this.communityId, this.spaceId); + + @override + List get props => [communityId, spaceId]; +} + +class SearchForSpace extends SpaceTreeEvent { + final String searchQuery; + + const SearchForSpace(this.searchQuery); + + @override + List get props => [searchQuery]; +} diff --git a/lib/pages/space_tree/bloc/space_tree_state.dart b/lib/pages/space_tree/bloc/space_tree_state.dart new file mode 100644 index 00000000..ab82f2dc --- /dev/null +++ b/lib/pages/space_tree/bloc/space_tree_state.dart @@ -0,0 +1,31 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; + +class SpaceTreeState extends Equatable { + final List spacesList; + final String selectedSpace; + final String selectedCommunity; + + const SpaceTreeState( + {this.spacesList = const [], this.selectedSpace = '', this.selectedCommunity = ''}); + + SpaceTreeState copyWith( + {List? communitiesList, String? selectedSpace, String? selectedCommunity}) { + return SpaceTreeState( + spacesList: communitiesList ?? this.spacesList, + selectedSpace: selectedSpace ?? this.selectedSpace, + selectedCommunity: selectedCommunity ?? this.selectedCommunity); + } + + @override + List get props => [spacesList, selectedSpace, selectedCommunity]; +} + +class SpaceTreeLoadingState extends SpaceTreeState {} + +class SpaceTreeErrorState extends SpaceTreeState { + final String message; + const SpaceTreeErrorState(this.message); + @override + List get props => [message]; +} diff --git a/lib/pages/space_tree/view/side_spaces_view.dart b/lib/pages/space_tree/view/side_spaces_view.dart new file mode 100644 index 00000000..c890ef69 --- /dev/null +++ b/lib/pages/space_tree/view/side_spaces_view.dart @@ -0,0 +1,26 @@ +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/bloc/space_tree_state.dart'; +import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; + +class SideSpacesView extends StatelessWidget { + final Function onSelectAction; + const SideSpacesView({required this.onSelectAction, super.key}); + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) {}, + builder: (context, state) { + return SpaceTreeView( + communities: state.spacesList, + selectedSpaceUuid: state.selectedSpace, + selectedCommunityId: state.selectedCommunity, + buildContext: context, + action: onSelectAction, + isLoading: state is SpaceTreeLoadingState, + ); + }); + } +} diff --git a/lib/pages/space_tree/view/space_tree_view.dart b/lib/pages/space_tree/view/space_tree_view.dart new file mode 100644 index 00000000..59e7b73a --- /dev/null +++ b/lib/pages/space_tree/view/space_tree_view.dart @@ -0,0 +1,251 @@ +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'; +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/pages/spaces_management/all_spaces/widgets/community_tile.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class SpaceTreeView extends StatefulWidget { + final List communities; + final String? selectedCommunityId; + final String? selectedSpaceUuid; + final BuildContext buildContext; + final Function action; + final bool isLoading; + + const SpaceTreeView( + {super.key, + this.selectedCommunityId, + required this.communities, + this.selectedSpaceUuid, + required this.buildContext, + required this.action, + required this.isLoading}); + + @override + State createState() => _SpaceTreeViewState(); +} + +class _SpaceTreeViewState extends State { + String _searchQuery = ''; // Track search query + String? _selectedSpaceUuid; + String? _selectedCommunityId; + String? _expandedId; + + @override + void initState() { + super.initState(); + // _selectedId = widget.selectedSpaceUuid; // Initialize with the passed selected space UUID + _selectedCommunityId = widget.selectedCommunityId; + _selectedSpaceUuid = widget.selectedSpaceUuid; + } + + @override + void didUpdateWidget(covariant SpaceTreeView oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.selectedSpaceUuid != oldWidget.selectedSpaceUuid || + widget.selectedCommunityId != oldWidget.selectedCommunityId) { + setState(() { + // _selectedId = widget.selectedSpaceUuid; + _selectedCommunityId = widget.selectedCommunityId; + _selectedSpaceUuid = widget.selectedSpaceUuid; + }); + } + } + + // Function to filter communities based on the search query + List _filterCommunities() { + _expandedId = null; + if (_searchQuery.isEmpty) { + // Reset the selected community and space UUIDs if there's no query + // _selectedSpaceUuid = null; + // _selectedCommunityId = null; + return widget.communities; + } + + // Filter communities and expand only those that match the query + return widget.communities.where((community) { + final containsQueryInCommunity = + community.name.toLowerCase().contains(_searchQuery.toLowerCase()); + final containsQueryInSpaces = + community.spaces.any((space) => _containsQuery(space, _searchQuery.toLowerCase())); + + return containsQueryInCommunity || containsQueryInSpaces; + }).toList(); + } + + // Helper function to determine if any space or its children match the search query + bool _containsQuery(SpaceModel space, String query) { + final matchesSpace = space.name.toLowerCase().contains(query); + final matchesChildren = + space.children.any((child) => _containsQuery(child, query)); // Recursive check for children + + // If the space or any of its children match the query, expand this space + if (matchesSpace || matchesChildren) { + _expandedId = space.uuid; + } + + return matchesSpace || matchesChildren; + } + + bool _isSpaceOrChildSelected(SpaceModel space) { + // Return true if the current space or any of its child spaces is selected + if (_expandedId == space.uuid) { + return true; + } + + // Recursively check if any child spaces match the query + for (var child in space.children) { + if (_isSpaceOrChildSelected(child)) { + return true; + } + } + + return false; + } + + @override + Widget build(BuildContext context) { + final filteredCommunities = _filterCommunities(); + + return Container( + // width: MediaQuery.sizeOf(context).width * 0.25, + height: MediaQuery.sizeOf(context).height, + margin: const EdgeInsets.only(right: 24), + decoration: subSectionContainerDecoration, + child: widget.isLoading + ? const Center(child: CircularProgressIndicator()) + : Column( + mainAxisSize: MainAxisSize.min, // Ensures the Column only takes necessary height + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Communities title with the add button + Container( + decoration: subSectionContainerDecoration, + padding: const EdgeInsets.all(16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Communities', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Colors.black, + )), + GestureDetector( + onTap: () => _navigateToBlank(context), + child: Container( + width: 30, + height: 30, + decoration: const BoxDecoration( + color: ColorsManager.whiteColors, + shape: BoxShape.circle, + ), + child: Center( + child: SvgPicture.asset( + Assets.roundedAddIcon, + width: 24, + height: 24, + ), + ), + ), + ), + ], + ), + ), + // Search bar + CustomSearchBar( + onSearchChanged: (query) { + setState(() { + _selectedSpaceUuid = null; + _searchQuery = query; + }); + }, + ), + const SizedBox(height: 16), + // Community list + Expanded( + child: ListView( + children: filteredCommunities.map((community) { + return _buildCommunityTile(context, community); + }).toList(), + ), + ), + ], + ), + ); + } + + void _navigateToBlank(BuildContext context) { + setState(() { + // _selectedId = ''; + _selectedSpaceUuid = ''; + }); + // context.read().add( + // NewCommunityEvent(communities: widget.communities), + // ); + } + + Widget _buildCommunityTile(BuildContext context, CommunityModel community) { + bool hasChildren = community.spaces.isNotEmpty; + + return CommunityTile( + title: community.name, + key: ValueKey(community.uuid), + isSelected: _selectedCommunityId == community.uuid, + isExpanded: false, + onItemSelected: () { + setState(() { + _selectedCommunityId = community.uuid; + _selectedSpaceUuid = null; + }); + + widget.buildContext.read().add( + OnSelectSpaceEvent(community.uuid, ''), + ); + + widget.action(community.uuid, ''); + }, + onExpansionChanged: (String title, bool expanded) { + _handleExpansionChange(community.uuid, expanded); + }, + children: hasChildren + ? community.spaces.map((space) => _buildSpaceTile(space, community)).toList() + : null, + ); + } + + Widget _buildSpaceTile(SpaceModel space, CommunityModel community) { + return SpaceTile( + title: space.name, + key: ValueKey(space.uuid), + isSelected: _selectedSpaceUuid == space.uuid, + initiallyExpanded: _isSpaceOrChildSelected(space), + onExpansionChanged: (bool expanded) { + _handleExpansionChange(space.uuid ?? '', expanded); + }, + onItemSelected: () { + setState(() { + _selectedSpaceUuid = space.uuid; + _selectedCommunityId = community.uuid; + }); + + widget.buildContext.read().add( + OnSelectSpaceEvent(community.uuid, space.uuid ?? ''), + ); + + widget.action(community.uuid, space.uuid); + }, + children: space.children.isNotEmpty + ? space.children.map((childSpace) => _buildSpaceTile(childSpace, community)).toList() + : [], + ); + } + + void _handleExpansionChange(String uuid, bool expanded) {} +} diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_tile.dart b/lib/pages/spaces_management/all_spaces/widgets/community_tile.dart index 5b9f79f2..69a723c0 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_tile.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_tile.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/common/custom_expansion_tile.dart'; +import 'package:syncrow_web/common/widgets/custom_expansion_tile.dart'; class CommunityTile extends StatelessWidget { final String title; @@ -24,7 +24,7 @@ class CommunityTile extends StatelessWidget { return CustomExpansionTile( title: title, initiallyExpanded: isExpanded, - isSelected: isSelected, + isSelected: isSelected, onExpansionChanged: (bool expanded) { onExpansionChanged(title, expanded); }, 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 a58d73e9..99ce3b1d 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:syncrow_web/common/search_bar.dart'; +import 'package:syncrow_web/common/widgets/search_bar.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'; @@ -34,8 +34,7 @@ class _SidebarWidgetState extends State { @override void initState() { super.initState(); - _selectedId = widget - .selectedSpaceUuid; // Initialize with the passed selected space UUID + _selectedId = widget.selectedSpaceUuid; // Initialize with the passed selected space UUID } @override @@ -60,8 +59,8 @@ class _SidebarWidgetState extends State { return widget.communities.where((community) { final containsQueryInCommunity = community.name.toLowerCase().contains(_searchQuery.toLowerCase()); - final containsQueryInSpaces = community.spaces - .any((space) => _containsQuery(space, _searchQuery.toLowerCase())); + final containsQueryInSpaces = + community.spaces.any((space) => _containsQuery(space, _searchQuery.toLowerCase())); return containsQueryInCommunity || containsQueryInSpaces; }).toList(); @@ -70,8 +69,8 @@ class _SidebarWidgetState extends State { // Helper function to determine if any space or its children match the search query bool _containsQuery(SpaceModel space, String query) { final matchesSpace = space.name.toLowerCase().contains(query); - final matchesChildren = space.children.any((child) => - _containsQuery(child, query)); // Recursive check for children + final matchesChildren = + space.children.any((child) => _containsQuery(child, query)); // Recursive check for children // If the space or any of its children match the query, expand this space if (matchesSpace || matchesChildren) { @@ -105,8 +104,7 @@ class _SidebarWidgetState extends State { width: 300, decoration: subSectionContainerDecoration, child: Column( - mainAxisSize: - MainAxisSize.min, // Ensures the Column only takes necessary height + mainAxisSize: MainAxisSize.min, // Ensures the Column only takes necessary height crossAxisAlignment: CrossAxisAlignment.start, children: [ // Communities title with the add button @@ -194,9 +192,7 @@ class _SidebarWidgetState extends State { _handleExpansionChange(community.uuid, expanded); }, children: hasChildren - ? community.spaces - .map((space) => _buildSpaceTile(space, community)) - .toList() + ? community.spaces.map((space) => _buildSpaceTile(space, community)).toList() : null, // Render spaces within the community ); } @@ -218,14 +214,11 @@ class _SidebarWidgetState extends State { }); context.read().add( - SelectSpaceEvent( - selectedCommunity: community, selectedSpace: space), + SelectSpaceEvent(selectedCommunity: community, selectedSpace: space), ); }, children: space.children.isNotEmpty - ? space.children - .map((childSpace) => _buildSpaceTile(childSpace, community)) - .toList() + ? space.children.map((childSpace) => _buildSpaceTile(childSpace, community)).toList() : [], // Recursively render child spaces if available ); } diff --git a/lib/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart index 80753f9d..63cf9b7d 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/common/custom_expansion_tile.dart'; +import 'package:syncrow_web/common/widgets/custom_expansion_tile.dart'; class SpaceTile extends StatefulWidget { final String title; diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 5be1a9e4..cdddbbeb 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -9,12 +9,18 @@ import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_ import 'package:syncrow_web/pages/visitor_password/model/device_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 DevicesManagementApi { - Future> fetchDevices() async { + Future> fetchDevices(String communityId, String spaceId) async { try { final response = await HTTPService().get( - path: ApiEndpoints.getAllDevices, + path: communityId.isNotEmpty && spaceId.isNotEmpty + ? ApiEndpoints.getSpaceDevices + .replaceAll('{spaceUuid}', spaceId) + .replaceAll('{communityUuid}', communityId) + .replaceAll('{projectId}', TempConst.projectId) + : ApiEndpoints.getAllDevices, showServerMessage: true, expectedResponseModel: (json) { List jsonData = json; @@ -85,8 +91,7 @@ class DevicesManagementApi { } } - Future deviceBatchControl( - List uuids, String code, dynamic value) async { + Future deviceBatchControl(List uuids, String code, dynamic value) async { try { final body = { 'devicesUuid': uuids, @@ -110,8 +115,7 @@ class DevicesManagementApi { } } - static Future> getDevicesByGatewayId( - String gatewayId) async { + static Future> getDevicesByGatewayId(String gatewayId) async { final response = await HTTPService().get( path: ApiEndpoints.gatewayApi.replaceAll('{gatewayUuid}', gatewayId), showServerMessage: false, @@ -145,9 +149,7 @@ class DevicesManagementApi { String code, ) async { final response = await HTTPService().get( - path: ApiEndpoints.getDeviceLogs - .replaceAll('{uuid}', uuid) - .replaceAll('{code}', code), + path: ApiEndpoints.getDeviceLogs.replaceAll('{uuid}', uuid).replaceAll('{code}', code), showServerMessage: false, expectedResponseModel: (json) { return DeviceReport.fromJson(json); @@ -220,8 +222,7 @@ class DevicesManagementApi { } } - Future addScheduleRecord( - ScheduleEntry sendSchedule, String uuid) async { + Future addScheduleRecord(ScheduleEntry sendSchedule, String uuid) async { try { final response = await HTTPService().post( path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), @@ -238,8 +239,7 @@ class DevicesManagementApi { } } - Future> getDeviceSchedules( - String uuid, String category) async { + Future> getDeviceSchedules(String uuid, String category) async { try { final response = await HTTPService().get( path: ApiEndpoints.getScheduleByDeviceId @@ -262,9 +262,7 @@ class DevicesManagementApi { } Future updateScheduleRecord( - {required bool enable, - required String uuid, - required String scheduleId}) async { + {required bool enable, required String uuid, required String scheduleId}) async { try { final response = await HTTPService().put( path: ApiEndpoints.updateScheduleByDeviceId @@ -285,8 +283,7 @@ class DevicesManagementApi { } } - Future editScheduleRecord( - String uuid, ScheduleEntry newSchedule) async { + Future editScheduleRecord(String uuid, ScheduleEntry newSchedule) async { try { final response = await HTTPService().put( path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index 7be3ff11..551951ea 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart'; -import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart'; -import 'package:syncrow_web/pages/routiens/models/icon_model.dart'; -import 'package:syncrow_web/pages/routiens/models/routine_details_model.dart'; -import 'package:syncrow_web/pages/routiens/models/routine_model.dart'; +import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart'; +import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_scene_model.dart'; +import 'package:syncrow_web/pages/routines/models/icon_model.dart'; +import 'package:syncrow_web/pages/routines/models/routine_details_model.dart'; +import 'package:syncrow_web/pages/routines/models/routine_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'; @@ -12,8 +12,7 @@ class SceneApi { static final HTTPService _httpService = HTTPService(); // //create scene - static Future> createScene( - CreateSceneModel createSceneModel) async { + static Future> createScene(CreateSceneModel createSceneModel) async { try { debugPrint('create scene model: ${createSceneModel.toMap()}'); final response = await _httpService.post( @@ -70,13 +69,12 @@ class SceneApi { //get scenes by community id and space id - static Future> getScenesByUnitId( - String unitId, String communityId, + static Future> getScenes(String spaceId, String communityId, {showInDevice = false}) async { try { final response = await _httpService.get( - path: ApiEndpoints.getUnitScenes - .replaceAll('{spaceUuid}', unitId) + path: ApiEndpoints.getScenes + .replaceAll('{spaceUuid}', spaceId) .replaceAll('{communityUuid}', communityId) .replaceAll('{projectId}', TempConst.projectId), queryParameters: {'showInHomePage': showInDevice}, @@ -99,10 +97,10 @@ class SceneApi { //getAutomation - static Future> getAutomationByUnitId(String unitId) async { + static Future> getAutomation(String spaceId) async { try { final response = await _httpService.get( - path: ApiEndpoints.getSpaceAutomation.replaceAll('{unitUuid}', unitId), + path: ApiEndpoints.getSpaceAutomation.replaceAll('{spaceUuid}', spaceId), showServerMessage: false, expectedResponseModel: (json) { List scenes = []; @@ -132,12 +130,10 @@ class SceneApi { // } //automation details - static Future getAutomationDetails( - String automationId) async { + static Future getAutomationDetails(String automationId) async { try { final response = await _httpService.get( - path: ApiEndpoints.getAutomationDetails - .replaceAll('{automationId}', automationId), + path: ApiEndpoints.getAutomationDetails.replaceAll('{automationId}', automationId), showServerMessage: false, expectedResponseModel: (json) => RoutineDetailsModel.fromMap(json), ); @@ -152,8 +148,7 @@ class SceneApi { try { final response = await _httpService.put( path: ApiEndpoints.updateScene.replaceAll('{sceneId}', sceneId), - body: createSceneModel - .toJson(sceneId.isNotEmpty == true ? sceneId : null), + body: createSceneModel.toJson(sceneId.isNotEmpty == true ? sceneId : null), expectedResponseModel: (json) { return json; }, @@ -165,14 +160,11 @@ class SceneApi { } //update automation - static updateAutomation( - CreateAutomationModel createAutomationModel, String automationId) async { + static updateAutomation(CreateAutomationModel createAutomationModel, String automationId) async { try { final response = await _httpService.put( - path: ApiEndpoints.updateAutomation - .replaceAll('{automationId}', automationId), - body: createAutomationModel - .toJson(automationId.isNotEmpty == true ? automationId : null), + path: ApiEndpoints.updateAutomation.replaceAll('{automationId}', automationId), + body: createAutomationModel.toJson(automationId.isNotEmpty == true ? automationId : null), expectedResponseModel: (json) { return json; }, @@ -189,8 +181,7 @@ class SceneApi { final response = await _httpService.get( path: ApiEndpoints.getScene.replaceAll('{sceneId}', sceneId), showServerMessage: false, - expectedResponseModel: (json) => - RoutineDetailsModel.fromMap(json['data']), + expectedResponseModel: (json) => RoutineDetailsModel.fromMap(json['data']), ); return response; } catch (e) { @@ -199,8 +190,7 @@ class SceneApi { } //delete Scene - static Future deleteScene( - {required String unitUuid, required String sceneId}) async { + static Future deleteScene({required String unitUuid, required String sceneId}) async { try { final response = await _httpService.delete( path: ApiEndpoints.deleteScene diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index b8d23259..c5fc4506 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -25,6 +25,8 @@ abstract class ApiEndpoints { ////// Devices Management //////////////// static const String getAllDevices = '/device'; + static const String getSpaceDevices = + '/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/devices'; static const String getDeviceStatus = '/device/{uuid}/functions/status'; static const String getBatchStatus = '/device/status/batch'; @@ -38,8 +40,10 @@ abstract class ApiEndpoints { // Space Module 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 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'; @@ -63,18 +67,19 @@ abstract class ApiEndpoints { //product static const String listProducts = '/products'; - static const String getSpaceScenes = '/scene/tap-to-run/{unitUuid}'; - static const String getSpaceAutomation = '/automation/{unitUuid}'; + static const String getSpaceScenes = '/scene/tap-to-run/{spaceUuid}'; + static const String getSpaceAutomation = '/automation/{spaceUuid}'; static const String getIconScene = '/scene/icon'; static const String createScene = '/scene/tap-to-run'; static const String createAutomation = '/automation'; - static const String getUnitScenes = '/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/scenes'; + static const String getScenes = + '/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/scenes'; 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}'; static const String deleteAutomation = '/automation/{automationId}'; - static const String updateScene = '/scene/tap-to-run/{sceneId}'; + static const String updateScene = '/scene/tap-to-run/{sceneId}'; static const String updateAutomation = '/automation/{automationId}'; } From 540f569b1f58464aea7e69812b050fdb7059c028 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Sun, 5 Jan 2025 00:21:10 +0300 Subject: [PATCH 034/175] Added spaces devices --- .../device_managment_bloc.dart | 10 +- .../view/device_managment_page.dart | 2 +- .../widgets/device_managment_body.dart | 248 +++++++++--------- 3 files changed, 131 insertions(+), 129 deletions(-) diff --git a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart index 1fa61fc2..db1dd2c7 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart @@ -17,8 +17,6 @@ class DeviceManagementBloc extends Bloc(_onFetchDevices); @@ -34,13 +32,7 @@ class DeviceManagementBloc extends Bloc _onFetchDevices(FetchDevices event, Emitter emit) async { emit(DeviceManagementLoading()); try { - if (event.communityId.isNotEmpty) { - communityId = event.communityId; - } - if (event.spaceId.isNotEmpty) { - spaceId = event.spaceId; - } - final devices = await DevicesManagementApi().fetchDevices(communityId ?? '', spaceId ?? ''); + final devices = await DevicesManagementApi().fetchDevices(event.communityId, event.spaceId); _selectedDevices.clear(); _devices = devices; _filteredDevices = devices; diff --git a/lib/pages/device_managment/all_devices/view/device_managment_page.dart b/lib/pages/device_managment/all_devices/view/device_managment_page.dart index f64ef734..114c1fac 100644 --- a/lib/pages/device_managment/all_devices/view/device_managment_page.dart +++ b/lib/pages/device_managment/all_devices/view/device_managment_page.dart @@ -80,7 +80,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { return BlocBuilder( builder: (context, deviceState) { if (deviceState is DeviceManagementLoading) { - return const Center(child: CircularProgressIndicator()); + return const DeviceManagementBody(devices: []); } else if (deviceState is DeviceManagementLoaded) { return DeviceManagementBody(devices: deviceState.devices); } else if (deviceState is DeviceManagementFiltered) { diff --git a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart index 9ae3b89f..af4e8039 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart @@ -69,133 +69,143 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { )), Flexible( flex: 3, - child: Column( - children: [ - Container( - padding: isLargeScreenSize(context) - ? const EdgeInsets.all(30) - : const EdgeInsets.all(15), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: state is DeviceManagementLoading + ? const Center(child: CircularProgressIndicator()) + : Column( children: [ - FilterWidget( - size: MediaQuery.of(context).size, - tabs: tabs, - selectedIndex: selectedIndex, - onTabChanged: (index) { - context.read().add(SelectedFilterChanged(index)); - }, - ), - const SizedBox(height: 20), - const DeviceSearchFilters(), - const SizedBox(height: 12), Container( - height: 45, - width: 125, - decoration: containerDecoration, - child: Center( - child: DefaultButton( - onPressed: isControlButtonEnabled - ? () { - if (selectedDevices.length == 1) { - showDialog( - context: context, - builder: (context) => DeviceControlDialog( - device: selectedDevices.first, - ), - ); - } else if (selectedDevices.length > 1) { - final productTypes = selectedDevices - .map((device) => device.productType) - .toSet(); - if (productTypes.length == 1) { - showDialog( - context: context, - builder: (context) => DeviceBatchControlDialog( - devices: selectedDevices, - ), - ); - } - } - } - : null, - borderRadius: 9, - child: Text( - buttonLabel, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - color: isControlButtonEnabled ? Colors.white : Colors.grey, + padding: isLargeScreenSize(context) + ? const EdgeInsets.all(30) + : const EdgeInsets.all(15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FilterWidget( + size: MediaQuery.of(context).size, + tabs: tabs, + selectedIndex: selectedIndex, + onTabChanged: (index) { + context + .read() + .add(SelectedFilterChanged(index)); + }, + ), + const SizedBox(height: 20), + const DeviceSearchFilters(), + const SizedBox(height: 12), + Container( + height: 45, + width: 125, + decoration: containerDecoration, + child: Center( + child: DefaultButton( + onPressed: isControlButtonEnabled + ? () { + if (selectedDevices.length == 1) { + showDialog( + context: context, + builder: (context) => DeviceControlDialog( + device: selectedDevices.first, + ), + ); + } else if (selectedDevices.length > 1) { + final productTypes = selectedDevices + .map((device) => device.productType) + .toSet(); + if (productTypes.length == 1) { + showDialog( + context: context, + builder: (context) => DeviceBatchControlDialog( + devices: selectedDevices, + ), + ); + } + } + } + : null, + borderRadius: 9, + child: Text( + buttonLabel, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: isControlButtonEnabled ? Colors.white : Colors.grey, + ), + ), + ), ), ), - ), + ], ), ), + Expanded( + child: Padding( + padding: isLargeScreenSize(context) + ? const EdgeInsets.all(30) + : const EdgeInsets.all(15), + child: DynamicTable( + withSelectAll: true, + cellDecoration: containerDecoration, + onRowSelected: (index, isSelected, row) { + final selectedDevice = devicesToShow[index]; + context + .read() + .add(SelectDevice(selectedDevice)); + }, + withCheckBox: true, + size: MediaQuery.of(context).size, + uuidIndex: 2, + headers: const [ + 'Device Name', + 'Product Name', + 'Device ID', + 'Space Name', + 'location', + 'Battery Level', + 'Installation Date and Time', + 'Status', + 'Last Offline Date and Time', + ], + data: devicesToShow.map((device) { + final combinedSpaceNames = device.spaces != null + ? device.spaces!.map((space) => space.spaceName).join(' > ') + + (device.community != null + ? ' > ${device.community!.name}' + : '') + : (device.community != null ? device.community!.name : ''); + + return [ + device.name ?? '', + device.productName ?? '', + device.uuid ?? '', + (device.spaces != null && device.spaces!.isNotEmpty) + ? device.spaces![0].spaceName + : '', + combinedSpaceNames, + device.batteryLevel != null ? '${device.batteryLevel}%' : '-', + formatDateTime(DateTime.fromMillisecondsSinceEpoch( + (device.createTime ?? 0) * 1000)), + device.online == true ? 'Online' : 'Offline', + formatDateTime(DateTime.fromMillisecondsSinceEpoch( + (device.updateTime ?? 0) * 1000)), + ]; + }).toList(), + onSelectionChanged: (selectedRows) { + context + .read() + .add(UpdateSelection(selectedRows)); + }, + initialSelectedIds: context + .read() + .selectedDevices + .map((device) => device.uuid!) + .toList(), + isEmpty: devicesToShow.isEmpty, + ), + ), + ) ], ), - ), - Expanded( - child: Padding( - padding: isLargeScreenSize(context) - ? const EdgeInsets.all(30) - : const EdgeInsets.all(15), - child: DynamicTable( - withSelectAll: true, - cellDecoration: containerDecoration, - onRowSelected: (index, isSelected, row) { - final selectedDevice = devicesToShow[index]; - context.read().add(SelectDevice(selectedDevice)); - }, - withCheckBox: true, - size: MediaQuery.of(context).size, - uuidIndex: 2, - headers: const [ - 'Device Name', - 'Product Name', - 'Device ID', - 'Space Name', - 'location', - 'Battery Level', - 'Installation Date and Time', - 'Status', - 'Last Offline Date and Time', - ], - data: devicesToShow.map((device) { - final combinedSpaceNames = device.spaces != null - ? device.spaces!.map((space) => space.spaceName).join(' > ') + - (device.community != null ? ' > ${device.community!.name}' : '') - : (device.community != null ? device.community!.name : ''); - - return [ - device.name ?? '', - device.productName ?? '', - device.uuid ?? '', - (device.spaces != null && device.spaces!.isNotEmpty) - ? device.spaces![0].spaceName - : '', - combinedSpaceNames, - device.batteryLevel != null ? '${device.batteryLevel}%' : '-', - formatDateTime(DateTime.fromMillisecondsSinceEpoch( - (device.createTime ?? 0) * 1000)), - device.online == true ? 'Online' : 'Offline', - formatDateTime(DateTime.fromMillisecondsSinceEpoch( - (device.updateTime ?? 0) * 1000)), - ]; - }).toList(), - onSelectionChanged: (selectedRows) { - context.read().add(UpdateSelection(selectedRows)); - }, - initialSelectedIds: context - .read() - .selectedDevices - .map((device) => device.uuid!) - .toList(), - isEmpty: devicesToShow.isEmpty, - ), - ), - ) - ], - ), ), ], ); From 90e5499f9222029b8e84efc30c8f4a830e9ad4e6 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 11:45:05 +0400 Subject: [PATCH 035/175] subspace model --- .env.development | 2 +- .gitignore | 3 + .../bloc/space_management_bloc.dart | 3 +- .../widgets/loaded_space_widget.dart | 3 +- .../bloc/create_space_model_bloc.dart | 26 +++ .../bloc/create_space_model_event.dart | 11 ++ .../bloc/create_space_model_state.dart | 19 ++ .../space_model/bloc/subspace_model_bloc.dart | 40 ++-- .../bloc/subspace_model_event.dart | 14 ++ .../bloc/subspace_model_state.dart | 11 ++ .../models/space_template_model.dart | 109 ++++++++--- .../space_model/view/space_model_page.dart | 7 +- .../dialog/create_space_model_dialog.dart | 174 +++++++++--------- .../dialog/create_subspace_model_dialog.dart | 19 +- .../widgets/space_model_card_widget.dart | 22 ++- lib/utils/constants/action_enum.dart | 31 ++++ 16 files changed, 346 insertions(+), 148 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart create mode 100644 lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart create mode 100644 lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart create mode 100644 lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart create mode 100644 lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart create mode 100644 lib/utils/constants/action_enum.dart diff --git a/.env.development b/.env.development index e77609dc..1fd358ec 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,2 @@ ENV_NAME=development -BASE_URL=https://syncrow-dev.azurewebsites.net \ No newline at end of file +BASE_URL=http://localhost:4001 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 29a3a501..ae866139 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release +lib/utils/constants/temp_const.dart +.env.development +lib/utils/constants/temp_const.dart diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 2434a32c..0c002e7d 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -329,6 +329,7 @@ class SpaceManagementBloc Emitter emit, ) { final communities = List.from(previousState.communities); + for (var community in communities) { if (community.uuid == communityUuid) { @@ -425,7 +426,7 @@ class SpaceManagementBloc emit(SpaceManagementLoading()); try { List communities = await _api.fetchCommunities(); - + List updatedCommunities = await Future.wait( communities.map((community) async { List spaces = 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 6bfd3ee3..5039340c 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 @@ -48,7 +48,8 @@ class _LoadedStateViewState extends State { hasSpaceModels ? Expanded( child: SpaceModelPage( - spaceModels: widget.spaceModels ??[], + spaceModels: widget.spaceModels ?? [], + products: widget.products, )) : CommunityStructureArea( selectedCommunity: widget.selectedCommunity, diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart new file mode 100644 index 00000000..e39d3b3d --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -0,0 +1,26 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +class CreateSpaceModelBloc extends Bloc { + SpaceTemplateModel? _space; + + CreateSpaceModelBloc() : super(CreateSpaceModelInitial()) { + on((event, emit) { + emit(CreateSpaceModelLoading()); + Future.delayed(const Duration(seconds: 1), () { + if (_space != null) { + emit(CreateSpaceModelLoaded(_space!)); + } else { + emit(CreateSpaceModelError("No space template found")); + } + }); + }); + + on((event, emit) { + _space = event.spaceTemplate; + emit(CreateSpaceModelLoaded(_space!)); + }); + } +} \ No newline at end of file diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart new file mode 100644 index 00000000..f3cee617 --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -0,0 +1,11 @@ +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +abstract class CreateSpaceModelEvent {} + +class LoadSpaceTemplate extends CreateSpaceModelEvent {} + +class UpdateSpaceTemplate extends CreateSpaceModelEvent { + final SpaceTemplateModel spaceTemplate; + + UpdateSpaceTemplate(this.spaceTemplate); +} diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart new file mode 100644 index 00000000..1a9f52bb --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart @@ -0,0 +1,19 @@ +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +abstract class CreateSpaceModelState {} + +class CreateSpaceModelInitial extends CreateSpaceModelState {} + +class CreateSpaceModelLoading extends CreateSpaceModelState {} + +class CreateSpaceModelLoaded extends CreateSpaceModelState { + final SpaceTemplateModel space; + + CreateSpaceModelLoaded(this.space); +} + +class CreateSpaceModelError extends CreateSpaceModelState { + final String message; + + CreateSpaceModelError(this.message); +} diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart index 423a5ca5..344d4b95 100644 --- a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart @@ -1,38 +1,34 @@ import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; - -// Events -abstract class SubSpaceModelEvent {} - -class AddSubSpaceModel extends SubSpaceModelEvent { - final SubspaceTemplateModel subSpace; - AddSubSpaceModel(this.subSpace); -} - -class RemoveSubSpaceModel extends SubSpaceModelEvent { - final SubspaceTemplateModel subSpace; - RemoveSubSpaceModel(this.subSpace); -} - -// State -class SubSpaceModelState { - final List subSpaces; - SubSpaceModelState(this.subSpaces); -} +import 'package:syncrow_web/utils/constants/action_enum.dart'; // BLoC class SubSpaceModelBloc extends Bloc { - SubSpaceModelBloc() : super(SubSpaceModelState([])) { + SubSpaceModelBloc() : super(SubSpaceModelState([], [])) { on((event, emit) { final updatedSubSpaces = List.from(state.subSpaces) ..add(event.subSpace); - emit(SubSpaceModelState(updatedSubSpaces)); + emit(SubSpaceModelState(updatedSubSpaces, [])); }); on((event, emit) { final updatedSubSpaces = List.from(state.subSpaces) ..remove(event.subSpace); - emit(SubSpaceModelState(updatedSubSpaces)); + + final updatedSubspaceModels = List.from( + state.updatedSubSpaceModels, + ); + + if (event.subSpace.uuid?.isNotEmpty ?? false) { + updatedSubspaceModels.add(UpdateSubspaceTemplateModel( + action: Action.delete, + uuid: event.subSpace.uuid!, + )); + } + + emit(SubSpaceModelState(updatedSubSpaces, updatedSubspaceModels)); }); } } diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart new file mode 100644 index 00000000..07d85643 --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart @@ -0,0 +1,14 @@ +// Events +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +abstract class SubSpaceModelEvent {} + +class AddSubSpaceModel extends SubSpaceModelEvent { + final SubspaceTemplateModel subSpace; + AddSubSpaceModel(this.subSpace); +} + +class RemoveSubSpaceModel extends SubSpaceModelEvent { + final SubspaceTemplateModel subSpace; + RemoveSubSpaceModel(this.subSpace); +} diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart new file mode 100644 index 00000000..1579f32f --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart @@ -0,0 +1,11 @@ +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +class SubSpaceModelState { + final List subSpaces; + final List updatedSubSpaceModels; + + SubSpaceModelState( + this.subSpaces, + this.updatedSubSpaceModels, + ); +} 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 2107b87f..a885ea6c 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 @@ -1,25 +1,20 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/utils/constants/action_enum.dart'; import 'package:uuid/uuid.dart'; class SpaceTemplateModel { - final String uuid; - final DateTime createdAt; - final DateTime updatedAt; + final String? uuid; final String modelName; - final bool disabled; - final List subspaceModels; - final List tags; + final List? subspaceModels; + final List? tags; String internalId; SpaceTemplateModel({ - required this.uuid, + this.uuid, String? internalId, - required this.createdAt, - required this.updatedAt, required this.modelName, - required this.disabled, - required this.subspaceModels, - required this.tags, + this.subspaceModels, + this.tags, }) : internalId = internalId ?? const Uuid().v4(); factory SpaceTemplateModel.fromJson(Map json) { @@ -28,28 +23,22 @@ class SpaceTemplateModel { return SpaceTemplateModel( uuid: json['uuid'] ?? '', internalId: internalId, - createdAt: DateTime.parse(json['createdAt']), - updatedAt: DateTime.parse(json['updatedAt']), modelName: json['modelName'] ?? '', - disabled: json['disabled'] ?? false, subspaceModels: (json['subspaceModels'] as List) - .map((item) => SubspaceTemplateModel.fromJson(item)) - .toList(), + .map((item) => SubspaceTemplateModel.fromJson(item)) + .toList(), tags: (json['tags'] as List) - .map((item) => TagModel.fromJson(item)) - .toList(), + .map((item) => TagModel.fromJson(item)) + .toList(), ); } Map toJson() { return { 'uuid': uuid, - 'createdAt': createdAt.toIso8601String(), - 'updatedAt': updatedAt.toIso8601String(), 'modelName': modelName, - 'disabled': disabled, - 'subspaceModels': subspaceModels.map((e) => e.toJson()).toList(), - 'tags': tags.map((e) => e.toJson()).toList(), + 'subspaceModels': subspaceModels?.map((e) => e.toJson()).toList(), + 'tags': tags?.map((e) => e.toJson()).toList(), }; } } @@ -129,3 +118,75 @@ class TagModel { }; } } + +class UpdateSubspaceTemplateModel { + final String uuid; + final Action action; + final String? subspaceName; + final List? tags; + + UpdateSubspaceTemplateModel({ + required this.action, + required this.uuid, + this.subspaceName, + this.tags, + }); + + factory UpdateSubspaceTemplateModel.fromJson(Map json) { + return UpdateSubspaceTemplateModel( + action: ActionExtension.fromValue(json['action']), + uuid: json['uuid'] ?? '', + subspaceName: json['subspaceName'] ?? '', + tags: (json['tags'] as List) + .map((item) => UpdateTagModel.fromJson(item)) + .toList(), + ); + } + + Map toJson() { + return { + 'action': action.value, + 'uuid': uuid, + 'subspaceName': subspaceName, + 'tags': tags?.map((e) => e.toJson()).toList() ?? [], + }; + } +} + +class UpdateTagModel { + final Action action; + final String? uuid; + final String tag; + final bool disabled; + final ProductModel? product; + + UpdateTagModel({ + required this.action, + this.uuid, + required this.tag, + required this.disabled, + this.product, + }); + + factory UpdateTagModel.fromJson(Map json) { + return UpdateTagModel( + action: ActionExtension.fromValue(json['action']), + uuid: json['uuid'] ?? '', + tag: json['tag'] ?? '', + disabled: json['disabled'] ?? false, + product: json['product'] != null + ? ProductModel.fromMap(json['product']) + : null, + ); + } + + Map toJson() { + return { + 'action': action.value, + 'uuid': uuid, + 'tag': tag, + 'disabled': disabled, + 'product': product?.toMap(), + }; + } +} diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 432713db..dd36350a 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.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/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart'; @@ -6,8 +7,10 @@ import 'package:syncrow_web/utils/color_manager.dart'; class SpaceModelPage extends StatelessWidget { final List spaceModels; + final List? products; - const SpaceModelPage({Key? key, required this.spaceModels}) : super(key: key); + const SpaceModelPage({Key? key, required this.spaceModels, this.products}) + : super(key: key); @override Widget build(BuildContext context) { @@ -33,7 +36,7 @@ class SpaceModelPage extends StatelessWidget { showDialog( context: context, builder: (BuildContext context) { - return const CreateSpaceModelDialog(); + return CreateSpaceModelDialog(products: products); }, ); }, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 4f0fff01..2d14af22 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -1,12 +1,15 @@ import 'package:flutter/material.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/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateSpaceModelDialog extends StatelessWidget { - const CreateSpaceModelDialog({Key? key}) : super(key: key); + final List? products; + + const CreateSpaceModelDialog({Key? key, this.products}) : super(key: key); @override Widget build(BuildContext context) { @@ -50,94 +53,54 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ), const SizedBox(height: 16), - GestureDetector( - onTap: () async { - final result = await showDialog( + TextButton( + onPressed: () async { + final result = await showDialog( + barrierDismissible: false, context: context, builder: (BuildContext context) { - return CreateSubSpaceModelDialog( + return const CreateSubSpaceModelDialog( isEdit: true, dialogTitle: 'Create Sub-space', - existingSubSpaces: [ - SubspaceTemplateModel( - subspaceName: "Living Room", - disabled: false, - uuid: "mkmkl,", - ), - ], ); }, ); - if (result == true) {} + if (result != null && result.isNotEmpty) { + // Handle the result if necessary + print('Subspace created: $result'); + } }, - child: SizedBox( - width: screenWidth * 0.25, - child: Container( - decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, - border: Border.all( - color: ColorsManager.neutralGray, - width: 3.0, - ), - borderRadius: BorderRadius.circular(20), - ), - child: const Padding( - padding: - EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), - child: Row( - children: [ - Icon( - Icons.add, - color: ColorsManager.spaceColor, - ), - SizedBox(width: 10), - Expanded( - child: Text( - 'Create sub space', - style: TextStyle( - color: ColorsManager.blackColor, - fontSize: 16, - ), - ), - ), - ], - ), - ), - ), + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + ), + child: _buildButtonContent( + context, + icon: Icons.add, + label: 'Create Sub Space', ), ), const SizedBox(height: 10), - SizedBox( - width: screenWidth * 0.25, - child: Container( - decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, - border: - Border.all(color: ColorsManager.neutralGray, width: 3.0), - borderRadius: BorderRadius.circular(20), - ), - child: const Padding( - padding: - EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), - child: Row( - children: [ - Icon( - Icons.add, - color: ColorsManager.spaceColor, - ), - SizedBox(width: 10), - Expanded( - child: Text( - 'Add devices', - style: TextStyle( - color: ColorsManager.blackColor, - fontSize: 16, - ), - ), - ), - ], + TextButton( + onPressed: () async { + final result = await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AddDeviceWidget( + products: products, ), - ), + ); + if (result == true) { + // Handle the result if necessary + print('Devices added successfully'); + } + }, + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + ), + child: _buildButtonContent( + context, + icon: Icons.add, + label: 'Add Devices', ), ), const SizedBox(height: 20), @@ -146,14 +109,17 @@ class CreateSpaceModelDialog extends StatelessWidget { child: Row( children: [ Expanded( - child: CancelButton( - label: 'Cancel', - onPressed: () => Navigator.of(context).pop(), - )), + child: CancelButton( + label: 'Cancel', + onPressed: () => Navigator.of(context).pop(), + ), + ), const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: () {}, + onPressed: () { + Navigator.of(context).pop(); + }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, foregroundColor: ColorsManager.whiteColors, @@ -162,10 +128,50 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ], ), - ) + ), ], ), ), ); } + + Widget _buildButtonContent(BuildContext context, + {required IconData icon, required String label}) { + final screenWidth = MediaQuery.of(context).size.width; + + return SizedBox( + width: screenWidth * 0.25, + child: Container( + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + border: Border.all( + color: ColorsManager.neutralGray, + width: 3.0, + ), + borderRadius: BorderRadius.circular(20), + ), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), + child: Row( + children: [ + Icon( + icon, + color: ColorsManager.spaceColor, + ), + const SizedBox(width: 10), + Expanded( + child: Text( + label, + style: const TextStyle( + color: ColorsManager.blackColor, + fontSize: 16, + ), + ), + ), + ], + ), + ), + ), + ); + } } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart index 9738f079..c4fec906 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart @@ -3,13 +3,15 @@ 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/space_model/bloc/subspace_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateSubSpaceModelDialog extends StatefulWidget { - final bool isEdit; // Flag to determine if it's edit or create - final String dialogTitle; // Title for the dialog - final List? existingSubSpaces; // For edit mode + final bool isEdit; + final String dialogTitle; + final List? existingSubSpaces; const CreateSubSpaceModelDialog({ super.key, @@ -52,8 +54,17 @@ class _CreateSubSpaceModelDialogState extends State { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), + child: BlocProvider( - create: (_) => SubSpaceModelBloc(), + create: (_) { + final bloc = SubSpaceModelBloc(); + if (widget.existingSubSpaces != null) { + for (var subSpace in widget.existingSubSpaces!) { + bloc.add(AddSubSpaceModel(subSpace)); + } + } + return bloc; + }, child: BlocBuilder( builder: (context, state) { return SizedBox( 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 550361c7..92d5735d 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 @@ -13,16 +13,20 @@ class SpaceModelCardWidget extends StatelessWidget { Widget build(BuildContext context) { final Map productTagCount = {}; - for (var tag in model.tags) { - final prodIcon = tag.product?.icon ?? 'Unknown'; - productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + if (model.tags != null) { + for (var tag in model.tags!) { + final prodIcon = tag.product?.icon ?? 'Unknown'; + productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + } } - for (var subspace in model.subspaceModels) { - if (subspace.tags != null) { - for (var tag in subspace.tags!) { - final prodIcon = tag.product?.icon ?? 'Unknown'; - productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + if (model.subspaceModels != null) { + for (var subspace in model.subspaceModels!) { + if (subspace.tags != null) { + for (var tag in subspace.tags!) { + final prodIcon = tag.product?.icon ?? 'Unknown'; + productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + } } } } @@ -65,7 +69,7 @@ class SpaceModelCardWidget extends StatelessWidget { spacing: 3.0, runSpacing: 3.0, children: [ - for (var subspace in model.subspaceModels.take(3)) + for (var subspace in model.subspaceModels!.take(3)) SubspaceChipWidget(subspace: subspace.subspaceName), ], ), diff --git a/lib/utils/constants/action_enum.dart b/lib/utils/constants/action_enum.dart new file mode 100644 index 00000000..2cfe53de --- /dev/null +++ b/lib/utils/constants/action_enum.dart @@ -0,0 +1,31 @@ +enum Action { + update, + add, + delete, +} + +extension ActionExtension on Action { + String get value { + switch (this) { + case Action.update: + return 'update'; + case Action.add: + return 'add'; + case Action.delete: + return 'delete'; + } + } + + static Action fromValue(String value) { + switch (value) { + case 'update': + return Action.update; + case 'add': + return Action.add; + case 'delete': + return Action.delete; + default: + throw ArgumentError('Invalid action: $value'); + } + } +} From 67ad986fcc49b97967688cec94e290f0f39a294a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 11:59:12 +0400 Subject: [PATCH 036/175] fixed issues in create space model --- .../space_model/bloc/subspace_model_bloc.dart | 22 ++++++- .../bloc/subspace_model_event.dart | 6 +- .../widgets/button_content_widget.dart | 53 +++++++++++++++ .../dialog/create_space_model_dialog.dart | 65 +++++-------------- .../dialog/create_subspace_model_dialog.dart | 55 +++++----------- 5 files changed, 109 insertions(+), 92 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/widgets/button_content_widget.dart diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart index 344d4b95..41c1d76f 100644 --- a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart @@ -10,7 +10,7 @@ class SubSpaceModelBloc extends Bloc { on((event, emit) { final updatedSubSpaces = List.from(state.subSpaces) ..add(event.subSpace); - emit(SubSpaceModelState(updatedSubSpaces, [])); + emit(SubSpaceModelState(updatedSubSpaces, state.updatedSubSpaceModels)); }); on((event, emit) { @@ -30,5 +30,25 @@ class SubSpaceModelBloc extends Bloc { emit(SubSpaceModelState(updatedSubSpaces, updatedSubspaceModels)); }); + + on((event, emit) { + final updatedSubSpaces = state.subSpaces.map((subSpace) { + if (subSpace.uuid == event.updatedSubSpace.uuid) { + return event.updatedSubSpace; + } + return subSpace; + }).toList(); + + final updatedSubspaceModels = List.from( + state.updatedSubSpaceModels, + ); + + updatedSubspaceModels.add(UpdateSubspaceTemplateModel( + action: Action.update, + uuid: event.updatedSubSpace.uuid!, + )); + + emit(SubSpaceModelState(updatedSubSpaces, updatedSubspaceModels)); + }); } } diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart index 07d85643..2147d128 100644 --- a/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart @@ -1,4 +1,3 @@ -// Events import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; abstract class SubSpaceModelEvent {} @@ -12,3 +11,8 @@ class RemoveSubSpaceModel extends SubSpaceModelEvent { final SubspaceTemplateModel subSpace; RemoveSubSpaceModel(this.subSpace); } + +class UpdateSubSpaceModel extends SubSpaceModelEvent { + final SubspaceTemplateModel updatedSubSpace; + UpdateSubSpaceModel(this.updatedSubSpace); +} diff --git a/lib/pages/spaces_management/space_model/widgets/button_content_widget.dart b/lib/pages/spaces_management/space_model/widgets/button_content_widget.dart new file mode 100644 index 00000000..81ecb674 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/button_content_widget.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class ButtonContentWidget extends StatelessWidget { + final IconData icon; + final String label; + + const ButtonContentWidget({ + Key? key, + required this.icon, + required this.label, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + return SizedBox( + width: screenWidth * 0.25, + child: Container( + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + border: Border.all( + color: ColorsManager.neutralGray, + width: 3.0, + ), + borderRadius: BorderRadius.circular(20), + ), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), + child: Row( + children: [ + Icon( + icon, + color: ColorsManager.spaceColor, + ), + const SizedBox(width: 10), + Expanded( + child: Text( + label, + style: const TextStyle( + color: ColorsManager.blackColor, + fontSize: 16, + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 2d14af22..77ca4e6d 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -3,6 +3,8 @@ 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/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.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/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -14,6 +16,7 @@ class CreateSpaceModelDialog extends StatelessWidget { @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; + List? subspaces = []; // Store subspaces here return AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), @@ -55,26 +58,28 @@ class CreateSpaceModelDialog extends StatelessWidget { const SizedBox(height: 16), TextButton( onPressed: () async { - final result = await showDialog( + final result = await showDialog>( barrierDismissible: false, context: context, builder: (BuildContext context) { - return const CreateSubSpaceModelDialog( + return CreateSubSpaceModelDialog( isEdit: true, dialogTitle: 'Create Sub-space', + existingSubSpaces: subspaces, ); }, ); - if (result != null && result.isNotEmpty) { - // Handle the result if necessary - print('Subspace created: $result'); + + if (result != null) { + // Update the subspaces + subspaces = result; + print('Updated Subspaces: $subspaces'); } }, style: TextButton.styleFrom( padding: EdgeInsets.zero, ), - child: _buildButtonContent( - context, + child: const ButtonContentWidget( icon: Icons.add, label: 'Create Sub Space', ), @@ -97,8 +102,7 @@ class CreateSpaceModelDialog extends StatelessWidget { style: TextButton.styleFrom( padding: EdgeInsets.zero, ), - child: _buildButtonContent( - context, + child: ButtonContentWidget( icon: Icons.add, label: 'Add Devices', ), @@ -118,7 +122,8 @@ class CreateSpaceModelDialog extends StatelessWidget { Expanded( child: DefaultButton( onPressed: () { - Navigator.of(context).pop(); + // Return data when OK is pressed + Navigator.of(context).pop(subspaces); }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, @@ -134,44 +139,4 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ); } - - Widget _buildButtonContent(BuildContext context, - {required IconData icon, required String label}) { - final screenWidth = MediaQuery.of(context).size.width; - - return SizedBox( - width: screenWidth * 0.25, - child: Container( - decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, - border: Border.all( - color: ColorsManager.neutralGray, - width: 3.0, - ), - borderRadius: BorderRadius.circular(20), - ), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), - child: Row( - children: [ - Icon( - icon, - color: ColorsManager.spaceColor, - ), - const SizedBox(width: 10), - Expanded( - child: Text( - label, - style: const TextStyle( - color: ColorsManager.blackColor, - fontSize: 16, - ), - ), - ), - ], - ), - ), - ), - ); - } } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart index c4fec906..31a1f0d0 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart @@ -8,58 +8,32 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_mo import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class CreateSubSpaceModelDialog extends StatefulWidget { +class CreateSubSpaceModelDialog extends StatelessWidget { final bool isEdit; final String dialogTitle; final List? existingSubSpaces; const CreateSubSpaceModelDialog({ - super.key, + Key? key, required this.isEdit, required this.dialogTitle, this.existingSubSpaces, - }); - - @override - _CreateSubSpaceModelDialogState createState() => - _CreateSubSpaceModelDialogState(); -} - -class _CreateSubSpaceModelDialogState extends State { - final TextEditingController textController = TextEditingController(); - final FocusNode focusNode = FocusNode(); - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) { - focusNode.requestFocus(); - }); - - if (widget.isEdit) {} - } - - @override - void dispose() { - textController.dispose(); - focusNode.dispose(); - super.dispose(); - } + }) : super(key: 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: (_) { final bloc = SubSpaceModelBloc(); - if (widget.existingSubSpaces != null) { - for (var subSpace in widget.existingSubSpaces!) { + if (existingSubSpaces != null) { + for (var subSpace in existingSubSpaces!) { bloc.add(AddSubSpaceModel(subSpace)); } } @@ -76,7 +50,7 @@ class _CreateSubSpaceModelDialogState extends State { mainAxisSize: MainAxisSize.min, children: [ Text( - widget.dialogTitle, + dialogTitle, style: Theme.of(context) .textTheme .headlineLarge @@ -97,15 +71,18 @@ class _CreateSubSpaceModelDialogState extends State { children: [ ...state.subSpaces.map( (subSpace) => Chip( - label: Text(subSpace.subspaceName, - style: const TextStyle( - color: ColorsManager.spaceColor)), + label: Text( + subSpace.subspaceName, + style: const TextStyle( + color: ColorsManager.spaceColor), + ), backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: const BorderSide( - color: ColorsManager.transparentColor, - width: 0), + color: ColorsManager.transparentColor, + width: 0, + ), ), deleteIcon: Container( width: 24, @@ -132,7 +109,6 @@ class _CreateSubSpaceModelDialogState extends State { width: 200, child: TextField( controller: textController, - focusNode: focusNode, decoration: InputDecoration( border: InputBorder.none, hintText: state.subSpaces.isEmpty @@ -148,7 +124,6 @@ class _CreateSubSpaceModelDialogState extends State { subspaceName: value.trim(), disabled: false))); textController.clear(); - focusNode.requestFocus(); } }, style: const TextStyle( From 1d35377137ce61689e9c5856db93fb80b7e3bb9b Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 12:07:48 +0400 Subject: [PATCH 037/175] added white background color to container --- .../space_model/bloc/subspace_model_bloc.dart | 44 +++- .../bloc/subspace_model_state.dart | 14 + .../dialog/create_space_model_dialog.dart | 2 +- .../dialog/create_subspace_model_dialog.dart | 246 +++++++++--------- 4 files changed, 182 insertions(+), 124 deletions(-) diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart index 41c1d76f..7455d8a5 100644 --- a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart @@ -4,15 +4,36 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_mo import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart'; -// BLoC class SubSpaceModelBloc extends Bloc { - SubSpaceModelBloc() : super(SubSpaceModelState([], [])) { + SubSpaceModelBloc() : super(SubSpaceModelState([], [], '')) { + // Handle AddSubSpaceModel Event on((event, emit) { - final updatedSubSpaces = List.from(state.subSpaces) - ..add(event.subSpace); - emit(SubSpaceModelState(updatedSubSpaces, state.updatedSubSpaceModels)); + // Check for duplicate names (case-insensitive) + final existingNames = + state.subSpaces.map((e) => e.subspaceName.toLowerCase()).toSet(); + + if (existingNames.contains(event.subSpace.subspaceName.toLowerCase())) { + // Emit state with an error message if duplicate name exists + emit(SubSpaceModelState( + state.subSpaces, + state.updatedSubSpaceModels, + 'Subspace name already exists.', + )); + } else { + // Add subspace if no duplicate exists + final updatedSubSpaces = + List.from(state.subSpaces) + ..add(event.subSpace); + + emit(SubSpaceModelState( + updatedSubSpaces, + state.updatedSubSpaceModels, + '', // Clear error message + )); + } }); + // Handle RemoveSubSpaceModel Event on((event, emit) { final updatedSubSpaces = List.from(state.subSpaces) ..remove(event.subSpace); @@ -28,9 +49,14 @@ class SubSpaceModelBloc extends Bloc { )); } - emit(SubSpaceModelState(updatedSubSpaces, updatedSubspaceModels)); + emit(SubSpaceModelState( + updatedSubSpaces, + updatedSubspaceModels, + '', // Clear error message + )); }); + // Handle UpdateSubSpaceModel Event on((event, emit) { final updatedSubSpaces = state.subSpaces.map((subSpace) { if (subSpace.uuid == event.updatedSubSpace.uuid) { @@ -48,7 +74,11 @@ class SubSpaceModelBloc extends Bloc { uuid: event.updatedSubSpace.uuid!, )); - emit(SubSpaceModelState(updatedSubSpaces, updatedSubspaceModels)); + emit(SubSpaceModelState( + updatedSubSpaces, + updatedSubspaceModels, + '', // Clear error message + )); }); } } diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart index 1579f32f..9026cb06 100644 --- a/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart @@ -3,9 +3,23 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/space_tem class SubSpaceModelState { final List subSpaces; final List updatedSubSpaceModels; + final String errorMessage; SubSpaceModelState( this.subSpaces, this.updatedSubSpaceModels, + this.errorMessage, ); + + SubSpaceModelState copyWith({ + List? subSpaces, + List? updatedSubSpaceModels, + String? errorMessage, + }) { + return SubSpaceModelState( + subSpaces ?? this.subSpaces, + updatedSubSpaceModels ?? this.updatedSubSpaceModels, + errorMessage ?? this.errorMessage, + ); + } } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 77ca4e6d..cdb4ab60 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -102,7 +102,7 @@ class CreateSpaceModelDialog extends StatelessWidget { style: TextButton.styleFrom( padding: EdgeInsets.zero, ), - child: ButtonContentWidget( + child: const ButtonContentWidget( icon: Icons.add, label: 'Add Devices', ), diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart index 31a1f0d0..c603b025 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart @@ -41,131 +41,145 @@ class CreateSubSpaceModelDialog extends StatelessWidget { }, child: BlocBuilder( builder: (context, state) { - return SizedBox( - width: screenWidth * 0.35, - 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.map( - (subSpace) => Chip( - label: Text( - subSpace.subspaceName, - style: const TextStyle( - color: ColorsManager.spaceColor), - ), - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: const BorderSide( - color: ColorsManager.transparentColor, - width: 0, + return Container( + color: ColorsManager.whiteColors, + child: SizedBox( + width: screenWidth * 0.35, + 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.map( + (subSpace) => Chip( + label: Text( + subSpace.subspaceName, + style: const TextStyle( + color: ColorsManager.spaceColor), + ), + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: const BorderSide( + color: 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)), ), ), - deleteIcon: Container( - width: 24, - height: 24, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: ColorsManager.lightGrayColor, - width: 1.5, + SizedBox( + width: 200, + child: TextField( + controller: textController, + decoration: InputDecoration( + border: InputBorder.none, + hintText: state.subSpaces.isEmpty + ? 'Please enter the name' + : null, + hintStyle: const TextStyle( + color: ColorsManager.lightGrayColor), + ), + onSubmitted: (value) { + if (value.trim().isNotEmpty) { + context.read().add( + AddSubSpaceModel( + SubspaceTemplateModel( + subspaceName: value.trim(), + disabled: false))); + textController.clear(); + } + }, + style: const TextStyle( + color: ColorsManager.blackColor), + ), + ), + if (state.errorMessage.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + state.errorMessage, + style: const TextStyle( + color: ColorsManager.warningRed, + fontSize: 12, + ), ), ), - 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: const TextStyle( - color: ColorsManager.lightGrayColor), - ), - onSubmitted: (value) { - if (value.trim().isNotEmpty) { - context.read().add( - AddSubSpaceModel(SubspaceTemplateModel( - subspaceName: value.trim(), - disabled: false))); - textController.clear(); - } - }, - style: const TextStyle( - color: ColorsManager.blackColor), - ), - ), - ], - ), - ), - const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: CancelButton( - label: 'Cancel', - onPressed: () { - Navigator.of(context).pop(); - }, + ], ), ), - const SizedBox(width: 10), - Expanded( - child: DefaultButton( - onPressed: () { - final subSpaces = context - .read() - .state - .subSpaces; - Navigator.of(context).pop(subSpaces); - }, - backgroundColor: ColorsManager.secondaryColor, - borderRadius: 10, - foregroundColor: ColorsManager.whiteColors, - child: const Text('OK'), - ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: CancelButton( + label: 'Cancel', + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + onPressed: () { + final subSpaces = context + .read() + .state + .subSpaces; + Navigator.of(context).pop(subSpaces); + }, + backgroundColor: ColorsManager.secondaryColor, + borderRadius: 10, + foregroundColor: ColorsManager.whiteColors, + child: const Text('OK'), + ), + ), + ], ), ], ), - ], - ), - ), - ); + ), + )); }, ), ), From 1c256cc55c697b4929e024663087ff53b046f43d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 12:25:34 +0400 Subject: [PATCH 038/175] added event for creating subspace --- .../bloc/create_space_model_bloc.dart | 20 ++++++++++++++-- .../bloc/create_space_model_event.dart | 6 +++++ .../models/space_template_model.dart | 24 +++++++++++++++---- .../dialog/create_space_model_dialog.dart | 8 ++++++- 4 files changed, 51 insertions(+), 7 deletions(-) diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index e39d3b3d..25d7c731 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -3,7 +3,8 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; -class CreateSpaceModelBloc extends Bloc { +class CreateSpaceModelBloc + extends Bloc { SpaceTemplateModel? _space; CreateSpaceModelBloc() : super(CreateSpaceModelInitial()) { @@ -22,5 +23,20 @@ class CreateSpaceModelBloc extends Bloc((event, emit) { + if (_space != null) { + final updatedSpace = _space!.copyWith( + subspaceModels: [ + ...(_space!.subspaceModels ?? []), + ...event.subspaces, + ], + ); + _space = updatedSpace; + emit(CreateSpaceModelLoaded(updatedSpace)); + } else { + emit(CreateSpaceModelError("Space template not initialized")); + } + }); } -} \ No newline at end of file +} diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index f3cee617..d3fe8f93 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -9,3 +9,9 @@ class UpdateSpaceTemplate extends CreateSpaceModelEvent { UpdateSpaceTemplate(this.spaceTemplate); } + +class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent { + final List subspaces; + + AddSubspacesToSpaceTemplate(this.subspaces); +} 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 a885ea6c..d6f8090e 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 @@ -25,11 +25,11 @@ class SpaceTemplateModel { internalId: internalId, modelName: json['modelName'] ?? '', subspaceModels: (json['subspaceModels'] as List) - .map((item) => SubspaceTemplateModel.fromJson(item)) - .toList(), + .map((item) => SubspaceTemplateModel.fromJson(item)) + .toList(), tags: (json['tags'] as List) - .map((item) => TagModel.fromJson(item)) - .toList(), + .map((item) => TagModel.fromJson(item)) + .toList(), ); } @@ -41,6 +41,22 @@ class SpaceTemplateModel { 'tags': tags?.map((e) => e.toJson()).toList(), }; } + + SpaceTemplateModel copyWith({ + String? uuid, + String? modelName, + List? subspaceModels, + List? tags, + String? internalId, + }) { + return SpaceTemplateModel( + uuid: uuid ?? this.uuid, + modelName: modelName ?? this.modelName, + subspaceModels: subspaceModels ?? this.subspaceModels, + tags: tags ?? this.tags, + internalId: internalId ?? this.internalId, + ); + } } class SubspaceTemplateModel { diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index cdb4ab60..c152fcc8 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -3,6 +3,8 @@ 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/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_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/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart'; @@ -73,7 +75,11 @@ class CreateSpaceModelDialog extends StatelessWidget { if (result != null) { // Update the subspaces subspaces = result; - print('Updated Subspaces: $subspaces'); + if (result.isNotEmpty) { + context + .read() + .add(AddSubspacesToSpaceTemplate(subspaces)); + } } }, style: TextButton.styleFrom( From 819670867d58537513e311aef461a9e4c23c74b1 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 12:54:35 +0400 Subject: [PATCH 039/175] separate widget --- .../space_model/view/space_model_page.dart | 7 +- .../dialog/create_space_model_dialog.dart | 127 +++++++++++++----- 2 files changed, 101 insertions(+), 33 deletions(-) diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index dd36350a..9eb72d89 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.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/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart'; @@ -36,7 +38,10 @@ class SpaceModelPage extends StatelessWidget { showDialog( context: context, builder: (BuildContext context) { - return CreateSpaceModelDialog(products: products); + return BlocProvider( + create: (_) => CreateSpaceModelBloc(), + child: CreateSpaceModelDialog(products: products), + ); }, ); }, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index c152fcc8..a5a27923 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -1,4 +1,5 @@ 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/all_spaces/model/product_model.dart'; @@ -58,38 +59,7 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ), const SizedBox(height: 16), - TextButton( - onPressed: () async { - final result = await showDialog>( - barrierDismissible: false, - context: context, - builder: (BuildContext context) { - return CreateSubSpaceModelDialog( - isEdit: true, - dialogTitle: 'Create Sub-space', - existingSubSpaces: subspaces, - ); - }, - ); - - if (result != null) { - // Update the subspaces - subspaces = result; - if (result.isNotEmpty) { - context - .read() - .add(AddSubspacesToSpaceTemplate(subspaces)); - } - } - }, - style: TextButton.styleFrom( - padding: EdgeInsets.zero, - ), - child: const ButtonContentWidget( - icon: Icons.add, - label: 'Create Sub Space', - ), - ), + _buildSubspacesSection(context, subspaces), const SizedBox(height: 10), TextButton( onPressed: () async { @@ -145,4 +115,97 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ); } + + Widget _buildSubspacesSection( + BuildContext context, List subspaces) { + return Container( + child: subspaces.isEmpty + ? TextButton( + style: TextButton.styleFrom( + overlayColor: Colors.transparent, + ), + onPressed: () async { + final result = await showDialog>( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return CreateSubSpaceModelDialog( + isEdit: true, + dialogTitle: subspaces.isEmpty? 'Create Sub-space': 'Edit Sub-space', + existingSubSpaces: subspaces, + ); + }, + ); + + if (result != null) { + subspaces.clear(); + subspaces.addAll(result); + context + .read() + .add(AddSubspacesToSpaceTemplate(subspaces)); + } + }, + child: const ButtonContentWidget( + icon: Icons.add, + label: 'Create Sub Space', + ), + ) + : Row( + children: [ + Expanded( + child: TextField( + readOnly: true, + decoration: InputDecoration( + filled: true, + fillColor: ColorsManager.whiteColors, + hintText: subspaces.map((e) => e.subspaceName).join(", "), + hintStyle: + const TextStyle(color: ColorsManager.spaceColor), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide.none, + ), + ), + ), + ), + const SizedBox(width: 10), + TextButton( + onPressed: () async { + final result = + await showDialog>( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return CreateSubSpaceModelDialog( + isEdit: true, + dialogTitle: 'Edit Sub-space', + existingSubSpaces: subspaces, + ); + }, + ); + + if (result != null) { + subspaces.clear(); + subspaces.addAll(result); + context + .read() + .add(AddSubspacesToSpaceTemplate(subspaces)); + } + }, + style: TextButton.styleFrom( + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide(color: ColorsManager.spaceColor), + ), + ), + child: const Text( + 'Edit', + style: TextStyle(color: ColorsManager.spaceColor), + ), + ), + ], + ), + ); + } } From 691beb2e86843835306f7e7cffe8d7aea3cf96aa Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 13:08:53 +0400 Subject: [PATCH 040/175] added bloc to widget --- .../space_model/view/space_model_page.dart | 5 +- .../dialog/create_space_model_dialog.dart | 182 ++++++++++-------- 2 files changed, 98 insertions(+), 89 deletions(-) diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 9eb72d89..3c601e47 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -38,10 +38,7 @@ class SpaceModelPage extends StatelessWidget { showDialog( context: context, builder: (BuildContext context) { - return BlocProvider( - create: (_) => CreateSpaceModelBloc(), - child: CreateSpaceModelDialog(products: products), - ); + return CreateSpaceModelDialog(products: products); }, ); }, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index a5a27923..aa4482d3 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -6,6 +6,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_mod import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_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/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart'; @@ -25,94 +26,103 @@ class CreateSpaceModelDialog extends StatelessWidget { shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), backgroundColor: ColorsManager.whiteColors, content: SizedBox( - width: screenWidth * 0.3, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Create New Space Model', - style: Theme.of(context) - .textTheme - .headlineLarge - ?.copyWith(color: ColorsManager.blackColor), - ), - const SizedBox(height: 16), - SizedBox( - width: screenWidth * 0.25, - child: TextField( - style: const TextStyle(color: ColorsManager.blackColor), - decoration: InputDecoration( - filled: true, - fillColor: ColorsManager.textFieldGreyColor, - hintText: 'Please enter the name', - hintStyle: - const TextStyle(color: ColorsManager.lightGrayColor), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: BorderSide.none, - ), - contentPadding: const EdgeInsets.symmetric( - vertical: 12.0, - horizontal: 16.0, - ), - ), - ), - ), - const SizedBox(height: 16), - _buildSubspacesSection(context, subspaces), - const SizedBox(height: 10), - TextButton( - onPressed: () async { - final result = await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => AddDeviceWidget( - products: products, - ), - ); - if (result == true) { - // Handle the result if necessary - print('Devices added successfully'); - } - }, - style: TextButton.styleFrom( - padding: EdgeInsets.zero, - ), - child: const ButtonContentWidget( - icon: Icons.add, - label: 'Add Devices', - ), - ), - const SizedBox(height: 20), - SizedBox( - width: screenWidth * 0.25, - child: Row( - children: [ - Expanded( - child: CancelButton( - label: 'Cancel', - onPressed: () => Navigator.of(context).pop(), + width: screenWidth * 0.3, + child: BlocProvider( + create: (_) { + final bloc = CreateSpaceModelBloc(); + return bloc; + }, + child: BlocBuilder( + builder: (context, state) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Create New Space Model', + style: Theme.of(context) + .textTheme + .headlineLarge + ?.copyWith(color: ColorsManager.blackColor), ), - ), - const SizedBox(width: 10), - Expanded( - child: DefaultButton( - onPressed: () { - // Return data when OK is pressed - Navigator.of(context).pop(subspaces); + const SizedBox(height: 16), + SizedBox( + width: screenWidth * 0.25, + child: TextField( + style: const TextStyle(color: ColorsManager.blackColor), + decoration: InputDecoration( + filled: true, + fillColor: ColorsManager.textFieldGreyColor, + hintText: 'Please enter the name', + hintStyle: const TextStyle( + color: ColorsManager.lightGrayColor), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.symmetric( + vertical: 12.0, + horizontal: 16.0, + ), + ), + ), + ), + const SizedBox(height: 16), + _buildSubspacesSection(context, subspaces), + const SizedBox(height: 10), + TextButton( + onPressed: () async { + final result = await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AddDeviceWidget( + products: products, + ), + ); + if (result == true) { + // Handle the result if necessary + print('Devices added successfully'); + } }, - backgroundColor: ColorsManager.secondaryColor, - borderRadius: 10, - foregroundColor: ColorsManager.whiteColors, - child: const Text('OK'), + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + ), + child: const ButtonContentWidget( + icon: Icons.add, + label: 'Add Devices', + ), ), - ), - ], - ), + const SizedBox(height: 20), + SizedBox( + width: screenWidth * 0.25, + child: Row( + children: [ + Expanded( + child: CancelButton( + label: 'Cancel', + onPressed: () => Navigator.of(context).pop(), + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + onPressed: () { + // Return data when OK is pressed + Navigator.of(context).pop(subspaces); + }, + backgroundColor: ColorsManager.secondaryColor, + borderRadius: 10, + foregroundColor: ColorsManager.whiteColors, + child: const Text('OK'), + ), + ), + ], + ), + ), + ], + ); + }, ), - ], - ), - ), + )), ); } @@ -131,7 +141,9 @@ class CreateSpaceModelDialog extends StatelessWidget { builder: (BuildContext context) { return CreateSubSpaceModelDialog( isEdit: true, - dialogTitle: subspaces.isEmpty? 'Create Sub-space': 'Edit Sub-space', + dialogTitle: subspaces.isEmpty + ? 'Create Sub-space' + : 'Edit Sub-space', existingSubSpaces: subspaces, ); }, From d2d5e76102efca2f8779cc9f6bbe541330069551 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 14:06:46 +0400 Subject: [PATCH 041/175] subspace model and view --- .../dialog/create_space_model_dialog.dart | 195 ++++++++++-------- 1 file changed, 110 insertions(+), 85 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index aa4482d3..23fce4cc 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -128,96 +128,121 @@ class CreateSpaceModelDialog extends StatelessWidget { Widget _buildSubspacesSection( BuildContext context, List subspaces) { + final screenWidth = MediaQuery.of(context).size.width; + return Container( - child: subspaces.isEmpty - ? TextButton( - style: TextButton.styleFrom( - overlayColor: Colors.transparent, - ), - onPressed: () async { - final result = await showDialog>( - barrierDismissible: false, - context: context, - builder: (BuildContext context) { - return CreateSubSpaceModelDialog( - isEdit: true, - dialogTitle: subspaces.isEmpty - ? 'Create Sub-space' - : 'Edit Sub-space', - existingSubSpaces: subspaces, - ); - }, - ); + child: subspaces.isEmpty + ? TextButton( + style: TextButton.styleFrom( + overlayColor: Colors.transparent, + ), + onPressed: () async { + final result = await showDialog>( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return CreateSubSpaceModelDialog( + isEdit: true, + dialogTitle: subspaces.isEmpty + ? 'Create Sub-space' + : 'Edit Sub-space', + existingSubSpaces: subspaces, + ); + }, + ); - if (result != null) { - subspaces.clear(); - subspaces.addAll(result); - context - .read() - .add(AddSubspacesToSpaceTemplate(subspaces)); - } - }, - child: const ButtonContentWidget( - icon: Icons.add, - label: 'Create Sub Space', - ), - ) - : Row( - children: [ - Expanded( - child: TextField( - readOnly: true, - decoration: InputDecoration( - filled: true, - fillColor: ColorsManager.whiteColors, - hintText: subspaces.map((e) => e.subspaceName).join(", "), - hintStyle: - const TextStyle(color: ColorsManager.spaceColor), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: BorderSide.none, + if (result != null) { + subspaces.clear(); + subspaces.addAll(result); + context + .read() + .add(AddSubspacesToSpaceTemplate(subspaces)); + } + }, + child: const ButtonContentWidget( + icon: Icons.add, + label: 'Create Sub Space', + ), + ) + : SizedBox( + width: screenWidth * 0.25, // Set the desired width + child: Container( + padding: const EdgeInsets.all( + 8.0), // Add padding around the content + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, // Background color + borderRadius: BorderRadius.circular(15), // Rounded corners + border: Border.all( + color: ColorsManager.textFieldGreyColor, // Border color + width: 3.0, // Border width + ), + ), + child: Wrap( + spacing: 8.0, // Spacing between chips + runSpacing: 8.0, // Spacing between rows of chips + children: [ + ...subspaces.map( + (subspace) => Chip( + label: Text( + subspace.subspaceName, + style: const TextStyle( + color: ColorsManager.spaceColor), // Text color + ), + backgroundColor: + Colors.white, // Chip background color + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(16), // Rounded chip + side: const BorderSide( + color: + ColorsManager.spaceColor), // Border color + ), + ), ), - ), - ), - ), - const SizedBox(width: 10), - TextButton( - onPressed: () async { - final result = - await showDialog>( - barrierDismissible: false, - context: context, - builder: (BuildContext context) { - return CreateSubSpaceModelDialog( - isEdit: true, - dialogTitle: 'Edit Sub-space', - existingSubSpaces: subspaces, - ); - }, - ); + GestureDetector( + onTap: () async { + final result = + await showDialog>( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return CreateSubSpaceModelDialog( + isEdit: true, + dialogTitle: 'Edit Sub-space', + existingSubSpaces: subspaces, + ); + }, + ); - if (result != null) { - subspaces.clear(); - subspaces.addAll(result); - context - .read() - .add(AddSubspacesToSpaceTemplate(subspaces)); - } - }, - style: TextButton.styleFrom( - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: BorderSide(color: ColorsManager.spaceColor), - ), - ), - child: const Text( - 'Edit', - style: TextStyle(color: ColorsManager.spaceColor), + if (result != null) { + subspaces.clear(); + subspaces.addAll(result); + context + .read() + .add(AddSubspacesToSpaceTemplate(subspaces)); + } + }, + child: Chip( + label: const Text( + 'Edit', + style: TextStyle( + color: ColorsManager + .spaceColor), // Text color for "Edit" + ), + backgroundColor: + Colors.white, // Background color for "Edit" + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(16), // Rounded chip + side: const BorderSide( + color: + ColorsManager.spaceColor), // Border color + ), + ), + ), + ], ), ), - ], - ), - ); + )); } } From 69092823a2c593f1db4317d4ae2a560c0544b06c Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 14:35:43 +0400 Subject: [PATCH 042/175] separation of widget --- .../dialog/create_space_model_dialog.dart | 126 +---------------- .../widgets/subspace_model_create_widget.dart | 133 ++++++++++++++++++ 2 files changed, 136 insertions(+), 123 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 23fce4cc..1e497f3d 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -5,11 +5,11 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_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/button_content_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart'; + +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateSpaceModelDialog extends StatelessWidget { @@ -67,7 +67,7 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ), const SizedBox(height: 16), - _buildSubspacesSection(context, subspaces), + SubspaceModelCreate(context, subspaces: subspaces), const SizedBox(height: 10), TextButton( onPressed: () async { @@ -125,124 +125,4 @@ class CreateSpaceModelDialog extends StatelessWidget { )), ); } - - Widget _buildSubspacesSection( - BuildContext context, List subspaces) { - final screenWidth = MediaQuery.of(context).size.width; - - return Container( - child: subspaces.isEmpty - ? TextButton( - style: TextButton.styleFrom( - overlayColor: Colors.transparent, - ), - onPressed: () async { - final result = await showDialog>( - barrierDismissible: false, - context: context, - builder: (BuildContext context) { - return CreateSubSpaceModelDialog( - isEdit: true, - dialogTitle: subspaces.isEmpty - ? 'Create Sub-space' - : 'Edit Sub-space', - existingSubSpaces: subspaces, - ); - }, - ); - - if (result != null) { - subspaces.clear(); - subspaces.addAll(result); - context - .read() - .add(AddSubspacesToSpaceTemplate(subspaces)); - } - }, - child: const ButtonContentWidget( - icon: Icons.add, - label: 'Create Sub Space', - ), - ) - : SizedBox( - width: screenWidth * 0.25, // Set the desired width - child: Container( - padding: const EdgeInsets.all( - 8.0), // Add padding around the content - decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, // Background color - borderRadius: BorderRadius.circular(15), // Rounded corners - border: Border.all( - color: ColorsManager.textFieldGreyColor, // Border color - width: 3.0, // Border width - ), - ), - child: Wrap( - spacing: 8.0, // Spacing between chips - runSpacing: 8.0, // Spacing between rows of chips - children: [ - ...subspaces.map( - (subspace) => Chip( - label: Text( - subspace.subspaceName, - style: const TextStyle( - color: ColorsManager.spaceColor), // Text color - ), - backgroundColor: - Colors.white, // Chip background color - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(16), // Rounded chip - side: const BorderSide( - color: - ColorsManager.spaceColor), // Border color - ), - ), - ), - GestureDetector( - onTap: () async { - final result = - await showDialog>( - barrierDismissible: false, - context: context, - builder: (BuildContext context) { - return CreateSubSpaceModelDialog( - isEdit: true, - dialogTitle: 'Edit Sub-space', - existingSubSpaces: subspaces, - ); - }, - ); - - if (result != null) { - subspaces.clear(); - subspaces.addAll(result); - context - .read() - .add(AddSubspacesToSpaceTemplate(subspaces)); - } - }, - child: Chip( - label: const Text( - 'Edit', - style: TextStyle( - color: ColorsManager - .spaceColor), // Text color for "Edit" - ), - backgroundColor: - Colors.white, // Background color for "Edit" - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(16), // Rounded chip - side: const BorderSide( - color: - ColorsManager.spaceColor), // Border color - ), - ), - ), - ], - ), - ), - )); - } } diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart new file mode 100644 index 00000000..df37810a --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -0,0 +1,133 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; + +class SubspaceModelCreate extends StatelessWidget { + final List subspaces; + + const SubspaceModelCreate(BuildContext context, { + Key? key, + required this.subspaces, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + return Container( + child: subspaces.isEmpty + ? TextButton( + style: TextButton.styleFrom( + overlayColor: ColorsManager.transparentColor, + ), + onPressed: () async { + final result = await showDialog>( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return CreateSubSpaceModelDialog( + isEdit: true, + dialogTitle: subspaces.isEmpty + ? 'Create Sub-space' + : 'Edit Sub-space', + existingSubSpaces: subspaces, + ); + }, + ); + + if (result != null) { + subspaces.clear(); + subspaces.addAll(result); + context + .read() + .add(AddSubspacesToSpaceTemplate(subspaces)); + } + }, + child: const ButtonContentWidget( + icon: Icons.add, + label: 'Create Sub Space', + ), + ) + : SizedBox( + width: screenWidth * 0.25, // Set the desired width + child: Container( + padding: const EdgeInsets.all(8.0), // Add padding + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, // Background color + borderRadius: BorderRadius.circular(15), // Rounded corners + border: Border.all( + color: ColorsManager.textFieldGreyColor, // Border color + width: 3.0, // Border width + ), + ), + child: Wrap( + spacing: 8.0, // Spacing between chips + runSpacing: 8.0, // Spacing between rows of chips + children: [ + ...subspaces.map( + (subspace) => Chip( + label: Text( + subspace.subspaceName, + style: const TextStyle( + color: ColorsManager.spaceColor), // Text color + ), + backgroundColor: Colors.white, // Chip background color + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(16), // Rounded chip + side: const BorderSide( + color: ColorsManager.spaceColor), // Border color + ), + ), + ), + GestureDetector( + onTap: () async { + final result = + await showDialog>( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return CreateSubSpaceModelDialog( + isEdit: true, + dialogTitle: 'Edit Sub-space', + existingSubSpaces: subspaces, + ); + }, + ); + + if (result != null) { + subspaces.clear(); + subspaces.addAll(result); + context + .read() + .add(AddSubspacesToSpaceTemplate(subspaces)); + } + }, + child: Chip( + label: const Text( + 'Edit', + style: TextStyle( + color: ColorsManager.spaceColor), // Text color + ), + backgroundColor: + Colors.white, // Background color for "Edit" + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(16), // Rounded chip + side: const BorderSide( + color: ColorsManager.spaceColor), // Border color + ), + ), + ), + ], + ), + ), + ), + ); + } +} From 0bed2573a02a94cb63f9658b94d9351a33935071 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 15:31:32 +0400 Subject: [PATCH 043/175] separation of models into different files --- .../bloc/create_space_model_event.dart | 1 + .../space_model/bloc/subspace_model_bloc.dart | 1 + .../bloc/subspace_model_event.dart | 2 +- .../bloc/subspace_model_state.dart | 1 + .../models/space_template_model.dart | 77 +----- .../models/subspace_template_model.dart | 35 +++ .../space_model/models/tag_model.dart | 44 ++++ .../dialog/add_device_type_model_widget.dart | 223 ++++++++++++++++++ .../dialog/create_space_model_dialog.dart | 6 +- .../dialog/create_subspace_model_dialog.dart | 2 +- .../widgets/subspace_model_create_widget.dart | 2 +- 11 files changed, 314 insertions(+), 80 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/models/subspace_template_model.dart create mode 100644 lib/pages/spaces_management/space_model/models/tag_model.dart create mode 100644 lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index d3fe8f93..6398c7ec 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -1,4 +1,5 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; abstract class CreateSpaceModelEvent {} diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart index 7455d8a5..4aa3dd82 100644 --- a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart @@ -2,6 +2,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_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/models/subspace_template_model.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart'; class SubSpaceModelBloc extends Bloc { diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart index 2147d128..63629472 100644 --- a/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart @@ -1,4 +1,4 @@ -import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; abstract class SubSpaceModelEvent {} diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart index 9026cb06..ab60a813 100644 --- a/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart @@ -1,4 +1,5 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; class SubSpaceModelState { final List subSpaces; 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 d6f8090e..51804178 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 @@ -1,4 +1,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart'; import 'package:uuid/uuid.dart'; @@ -59,81 +61,6 @@ class SpaceTemplateModel { } } -class SubspaceTemplateModel { - final String? uuid; - final String subspaceName; - final bool disabled; - final List? tags; - - SubspaceTemplateModel({ - this.uuid, - required this.subspaceName, - required this.disabled, - this.tags, - }); - - factory SubspaceTemplateModel.fromJson(Map json) { - return SubspaceTemplateModel( - uuid: json['uuid'] ?? '', - subspaceName: json['subspaceName'] ?? '', - disabled: json['disabled'] ?? false, - tags: (json['tags'] as List) - .map((item) => TagModel.fromJson(item)) - .toList(), - ); - } - - Map toJson() { - return { - 'uuid': uuid, - 'subspaceName': subspaceName, - 'disabled': disabled, - 'tags': tags?.map((e) => e.toJson()).toList() ?? [], - }; - } -} - -class TagModel { - final String uuid; - final DateTime createdAt; - final DateTime updatedAt; - final String tag; - final bool disabled; - final ProductModel? product; - - TagModel({ - required this.uuid, - required this.createdAt, - required this.updatedAt, - required this.tag, - required this.disabled, - this.product, - }); - - factory TagModel.fromJson(Map json) { - return TagModel( - uuid: json['uuid'] ?? '', - createdAt: DateTime.parse(json['createdAt']), - updatedAt: DateTime.parse(json['updatedAt']), - tag: json['tag'] ?? '', - disabled: json['disabled'] ?? false, - product: json['product'] != null - ? ProductModel.fromMap(json['product']) - : null, - ); - } - - Map toJson() { - return { - 'uuid': uuid, - 'createdAt': createdAt.toIso8601String(), - 'updatedAt': updatedAt.toIso8601String(), - 'tag': tag, - 'disabled': disabled, - 'product': product?.toMap(), - }; - } -} class UpdateSubspaceTemplateModel { final String uuid; diff --git a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart new file mode 100644 index 00000000..8f9d4d4d --- /dev/null +++ b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart @@ -0,0 +1,35 @@ +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; + +class SubspaceTemplateModel { + final String? uuid; + final String subspaceName; + final bool disabled; + final List? tags; + + SubspaceTemplateModel({ + this.uuid, + required this.subspaceName, + required this.disabled, + this.tags, + }); + + factory SubspaceTemplateModel.fromJson(Map json) { + return SubspaceTemplateModel( + uuid: json['uuid'] ?? '', + subspaceName: json['subspaceName'] ?? '', + disabled: json['disabled'] ?? false, + tags: (json['tags'] as List) + .map((item) => TagModel.fromJson(item)) + .toList(), + ); + } + + Map toJson() { + return { + 'uuid': uuid, + 'subspaceName': subspaceName, + 'disabled': disabled, + 'tags': tags?.map((e) => e.toJson()).toList() ?? [], + }; + } +} diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart new file mode 100644 index 00000000..356368fc --- /dev/null +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -0,0 +1,44 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; + +class TagModel { + final String uuid; + final DateTime createdAt; + final DateTime updatedAt; + final String tag; + final bool disabled; + final ProductModel? product; + + TagModel({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.tag, + required this.disabled, + this.product, + }); + + factory TagModel.fromJson(Map json) { + return TagModel( + uuid: json['uuid'] ?? '', + createdAt: DateTime.parse(json['createdAt']), + updatedAt: DateTime.parse(json['updatedAt']), + tag: json['tag'] ?? '', + disabled: json['disabled'] ?? false, + product: json['product'] != null + ? ProductModel.fromMap(json['product']) + : null, + ); + } + + Map toJson() { + return { + 'uuid': uuid, + 'createdAt': createdAt.toIso8601String(), + 'updatedAt': updatedAt.toIso8601String(), + 'tag': tag, + 'disabled': disabled, + 'product': product?.toMap(), + }; + } +} + diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart b/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart new file mode 100644 index 00000000..85285502 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart @@ -0,0 +1,223 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.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'; + +class AddDeviceTypeModelWidget extends StatefulWidget { + final List? products; + final ValueChanged>? onProductsSelected; + final List? initialSelectedProducts; + + const AddDeviceTypeModelWidget({ + super.key, + this.products, + this.initialSelectedProducts, + this.onProductsSelected, + }); + + @override + _AddDeviceWidgetState createState() => _AddDeviceWidgetState(); +} + +class _AddDeviceWidgetState extends State { + late final ScrollController _scrollController; + late List productCounts; + + @override + void initState() { + super.initState(); + _scrollController = ScrollController(); + productCounts = widget.initialSelectedProducts != null + ? List.from(widget.initialSelectedProducts!) + : []; + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + + // Adjust the GridView properties based on screen width + final crossAxisCount = size.width > 1200 + ? 8 + : size.width > 800 + ? 5 + : 3; + + return AlertDialog( + title: const Text('Add Devices'), + backgroundColor: ColorsManager.whiteColors, + content: SingleChildScrollView( + child: Container( + width: size.width * 0.9, + height: size.height * 0.65, + color: ColorsManager.textFieldGreyColor, + child: Column( + children: [ + const SizedBox(height: 16), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Scrollbar( + controller: _scrollController, + thumbVisibility: false, + child: GridView.builder( + shrinkWrap: true, + controller: _scrollController, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + mainAxisSpacing: 6, + crossAxisSpacing: 4, + childAspectRatio: .8, + ), + itemCount: widget.products?.length ?? 0, + itemBuilder: (context, index) { + final product = widget.products![index]; + return _buildDeviceTypeTile(product, size); + }, + ), + ), + ), + ), + ], + ), + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildActionButton( + 'Cancel', ColorsManager.boxColor, ColorsManager.blackColor, () { + Navigator.of(context).pop(); + }), + _buildActionButton( + 'Continue', ColorsManager.secondaryColor, Colors.white, () { + Navigator.of(context).pop(); + if (widget.onProductsSelected != null) { + widget.onProductsSelected!(productCounts); + } + }), + ], + ), + ], + ); + } + + Widget _buildDeviceTypeTile(ProductModel product, Size size) { + final selectedProduct = productCounts.firstWhere( + (p) => p.productId == product.uuid, + orElse: () => SelectedProduct(productId: product.uuid, count: 0), + ); + + return SizedBox( + width: size.width * 0.12, + height: size.height * 0.15, + child: Card( + elevation: 2, + color: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.all(4.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _buildDeviceIcon(product, size), + const SizedBox(height: 4), + _buildDeviceName(product, size), + const SizedBox(height: 4), + CounterWidget( + initialCount: selectedProduct.count, + onCountChanged: (newCount) { + setState(() { + if (newCount > 0) { + if (!productCounts.contains(selectedProduct)) { + productCounts.add(SelectedProduct( + productId: product.uuid, count: newCount)); + } else { + selectedProduct.count = newCount; + } + } else { + productCounts + .removeWhere((p) => p.productId == product.uuid); + } + + if (widget.onProductsSelected != null) { + widget.onProductsSelected!(productCounts); + } + }); + }, + ), + ], + ), + ), + ), + ); + } + + Widget _buildDeviceIcon(ProductModel product, Size size) { + return Container( + height: size.width > 800 ? 50 : 40, + width: size.width > 800 ? 50 : 40, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.textFieldGreyColor, + border: Border.all( + color: ColorsManager.neutralGray, + width: 2, + ), + ), + child: Center( + child: SvgPicture.asset( + product.icon ?? Assets.sensors, + width: size.width > 800 ? 30 : 20, + height: size.width > 800 ? 30 : 20, + ), + ), + ); + } + + Widget _buildDeviceName(ProductModel product, Size size) { + return SizedBox( + height: size.width > 800 ? 35 : 25, + child: Text( + product.name ?? '', + style: context.textTheme.bodySmall + ?.copyWith(color: ColorsManager.blackColor), + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ); + } + + Widget _buildActionButton( + String label, + Color backgroundColor, + Color foregroundColor, + VoidCallback onPressed, + ) { + return SizedBox( + width: 120, + child: DefaultButton( + onPressed: onPressed, + backgroundColor: backgroundColor, + foregroundColor: foregroundColor, + child: Text(label), + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 1e497f3d..e2095703 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -3,15 +3,17 @@ 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/all_spaces/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_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/button_content_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import '../../models/subspace_template_model.dart'; + class CreateSpaceModelDialog extends StatelessWidget { final List? products; @@ -74,7 +76,7 @@ class CreateSpaceModelDialog extends StatelessWidget { final result = await showDialog( barrierDismissible: false, context: context, - builder: (context) => AddDeviceWidget( + builder: (context) => AddDeviceTypeModelWidget( products: products, ), ); diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart index c603b025..65c85efa 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart @@ -5,7 +5,7 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_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/models/subspace_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateSubSpaceModelDialog extends StatelessWidget { diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index df37810a..0ccd929e 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; From ed06b0ebd6e6f25fba00eb30ee573947d062454f Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 15:37:20 +0400 Subject: [PATCH 044/175] converted to stateless --- .../bloc/add_device_model_bloc.dart | 30 +++ .../dialog/add_device_type_model_widget.dart | 173 ++++++++---------- 2 files changed, 106 insertions(+), 97 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/bloc/add_device_model_bloc.dart diff --git a/lib/pages/spaces_management/space_model/bloc/add_device_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/add_device_model_bloc.dart new file mode 100644 index 00000000..31c99dc4 --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/add_device_model_bloc.dart @@ -0,0 +1,30 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; + +class AddDeviceTypeModelBloc extends Cubit> { + AddDeviceTypeModelBloc(List initialProducts) + : super(initialProducts); + + void updateProductCount(String productId, int count) { + final existingProduct = state.firstWhere( + (p) => p.productId == productId, + orElse: () => SelectedProduct(productId: productId, count: 0), + ); + + if (count > 0) { + if (!state.contains(existingProduct)) { + emit([...state, SelectedProduct(productId: productId, count: count)]); + } else { + final updatedList = state.map((p) { + if (p.productId == productId) { + return SelectedProduct(productId: p.productId, count: count); + } + return p; + }).toList(); + emit(updatedList); + } + } else { + emit(state.where((p) => p.productId != productId).toList()); + } + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart b/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart index 85285502..06ec6bf4 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart @@ -1,14 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/add_device_model_bloc.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'; -class AddDeviceTypeModelWidget extends StatefulWidget { +class AddDeviceTypeModelWidget extends StatelessWidget { final List? products; final ValueChanged>? onProductsSelected; final List? initialSelectedProducts; @@ -20,101 +21,93 @@ class AddDeviceTypeModelWidget extends StatefulWidget { this.onProductsSelected, }); - @override - _AddDeviceWidgetState createState() => _AddDeviceWidgetState(); -} - -class _AddDeviceWidgetState extends State { - late final ScrollController _scrollController; - late List productCounts; - - @override - void initState() { - super.initState(); - _scrollController = ScrollController(); - productCounts = widget.initialSelectedProducts != null - ? List.from(widget.initialSelectedProducts!) - : []; - } - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; - - // Adjust the GridView properties based on screen width final crossAxisCount = size.width > 1200 ? 8 : size.width > 800 ? 5 : 3; - return AlertDialog( - title: const Text('Add Devices'), - backgroundColor: ColorsManager.whiteColors, - content: SingleChildScrollView( - child: Container( - width: size.width * 0.9, - height: size.height * 0.65, - color: ColorsManager.textFieldGreyColor, - child: Column( - children: [ - const SizedBox(height: 16), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: Scrollbar( - controller: _scrollController, - thumbVisibility: false, - child: GridView.builder( - shrinkWrap: true, - controller: _scrollController, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: crossAxisCount, - mainAxisSpacing: 6, - crossAxisSpacing: 4, - childAspectRatio: .8, + return BlocProvider( + create: (_) => AddDeviceTypeModelBloc( + initialSelectedProducts ?? []), // Initialize with initial products + child: AlertDialog( + title: const Text('Add Devices'), + backgroundColor: ColorsManager.whiteColors, + content: SingleChildScrollView( + child: Container( + width: size.width * 0.9, + height: size.height * 0.65, + color: ColorsManager.textFieldGreyColor, + child: Column( + children: [ + const SizedBox(height: 16), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Scrollbar( + thumbVisibility: false, + child: BlocBuilder>( + builder: (context, productCounts) { + return GridView.builder( + shrinkWrap: true, + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + mainAxisSpacing: 6, + crossAxisSpacing: 4, + childAspectRatio: .8, + ), + itemCount: products?.length ?? 0, + itemBuilder: (context, index) { + final product = products![index]; + return _buildDeviceTypeTile( + context, product, size, productCounts); + }, + ); + }, ), - itemCount: widget.products?.length ?? 0, - itemBuilder: (context, index) { - final product = widget.products![index]; - return _buildDeviceTypeTile(product, size); - }, ), ), ), + ], + ), + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildActionButton( + 'Cancel', + ColorsManager.boxColor, + ColorsManager.blackColor, + () => Navigator.of(context).pop(), + ), + _buildActionButton( + 'Continue', + ColorsManager.secondaryColor, + ColorsManager.whiteColors, + () { + Navigator.of(context).pop(); + if (onProductsSelected != null) { + final selectedProducts = + context.read().state; + onProductsSelected!(selectedProducts); + } + }, ), ], ), - ), + ], ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _buildActionButton( - 'Cancel', ColorsManager.boxColor, ColorsManager.blackColor, () { - Navigator.of(context).pop(); - }), - _buildActionButton( - 'Continue', ColorsManager.secondaryColor, Colors.white, () { - Navigator.of(context).pop(); - if (widget.onProductsSelected != null) { - widget.onProductsSelected!(productCounts); - } - }), - ], - ), - ], ); } - Widget _buildDeviceTypeTile(ProductModel product, Size size) { + Widget _buildDeviceTypeTile(BuildContext context, ProductModel product, + Size size, List productCounts) { final selectedProduct = productCounts.firstWhere( (p) => p.productId == product.uuid, orElse: () => SelectedProduct(productId: product.uuid, count: 0), @@ -137,28 +130,14 @@ class _AddDeviceWidgetState extends State { children: [ _buildDeviceIcon(product, size), const SizedBox(height: 4), - _buildDeviceName(product, size), + _buildDeviceName(context,product, size), const SizedBox(height: 4), CounterWidget( initialCount: selectedProduct.count, onCountChanged: (newCount) { - setState(() { - if (newCount > 0) { - if (!productCounts.contains(selectedProduct)) { - productCounts.add(SelectedProduct( - productId: product.uuid, count: newCount)); - } else { - selectedProduct.count = newCount; - } - } else { - productCounts - .removeWhere((p) => p.productId == product.uuid); - } - - if (widget.onProductsSelected != null) { - widget.onProductsSelected!(productCounts); - } - }); + context + .read() + .updateProductCount(product.uuid, newCount); }, ), ], @@ -190,12 +169,12 @@ class _AddDeviceWidgetState extends State { ); } - Widget _buildDeviceName(ProductModel product, Size size) { + Widget _buildDeviceName(BuildContext context, product, Size size) { return SizedBox( height: size.width > 800 ? 35 : 25, child: Text( product.name ?? '', - style: context.textTheme.bodySmall + style: Theme.of(context).textTheme.bodySmall ?.copyWith(color: ColorsManager.blackColor), textAlign: TextAlign.center, maxLines: 2, From b59e7e4836f311c54f8063afae4ea333b425dc68 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 15:41:03 +0400 Subject: [PATCH 045/175] reassign folders --- .../space_model/widgets/subspace_model_create_widget.dart | 2 +- .../bloc/subspace_model_bloc.dart | 4 ++-- .../bloc/subspace_model_event.dart | 0 .../bloc/subspace_model_state.dart | 0 .../views}/create_subspace_model_dialog.dart | 6 +++--- 5 files changed, 6 insertions(+), 6 deletions(-) rename lib/pages/spaces_management/{space_model => subspace_model}/bloc/subspace_model_bloc.dart (93%) rename lib/pages/spaces_management/{space_model => subspace_model}/bloc/subspace_model_event.dart (100%) rename lib/pages/spaces_management/{space_model => subspace_model}/bloc/subspace_model_state.dart (100%) rename lib/pages/spaces_management/{space_model/widgets/dialog => subspace_model/views}/create_subspace_model_dialog.dart (96%) diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 0ccd929e..4706fece 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/subspace_model/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart b/lib/pages/spaces_management/subspace_model/bloc/subspace_model_bloc.dart similarity index 93% rename from lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart rename to lib/pages/spaces_management/subspace_model/bloc/subspace_model_bloc.dart index 4aa3dd82..7feadc4f 100644 --- a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart +++ b/lib/pages/spaces_management/subspace_model/bloc/subspace_model_bloc.dart @@ -1,6 +1,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_event.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/subspace_model/bloc/subspace_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/subspace_model/bloc/subspace_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/models/subspace_template_model.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart'; diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart b/lib/pages/spaces_management/subspace_model/bloc/subspace_model_event.dart similarity index 100% rename from lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart rename to lib/pages/spaces_management/subspace_model/bloc/subspace_model_event.dart diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart b/lib/pages/spaces_management/subspace_model/bloc/subspace_model_state.dart similarity index 100% rename from lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart rename to lib/pages/spaces_management/subspace_model/bloc/subspace_model_state.dart diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart b/lib/pages/spaces_management/subspace_model/views/create_subspace_model_dialog.dart similarity index 96% rename from lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart rename to lib/pages/spaces_management/subspace_model/views/create_subspace_model_dialog.dart index 65c85efa..54981975 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/subspace_model/views/create_subspace_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/space_model/bloc/subspace_model_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_event.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/subspace_model/bloc/subspace_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/subspace_model/bloc/subspace_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/subspace_model/bloc/subspace_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; From ae09cbda1e422907802ca52457cad16d57f04633 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 15:59:11 +0400 Subject: [PATCH 046/175] separating folder --- .../dialog/add_device_type_model_widget.dart | 202 ------------------ .../dialog/create_space_model_dialog.dart | 2 +- .../bloc/add_device_model_bloc.dart | 0 .../views/add_device_type_model_widget.dart | 87 ++++++++ .../widgets/action_button_widget.dart | 30 +++ .../tag_model/widgets/device_icon_widget.dart | 36 ++++ .../tag_model/widgets/device_name_widget.dart | 28 +++ .../widgets/device_type_tile_widget.dart | 58 +++++ .../widgets/scrollable_grid_view_widget.dart | 49 +++++ 9 files changed, 289 insertions(+), 203 deletions(-) delete mode 100644 lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart rename lib/pages/spaces_management/{space_model => tag_model}/bloc/add_device_model_bloc.dart (100%) create mode 100644 lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart create mode 100644 lib/pages/spaces_management/tag_model/widgets/action_button_widget.dart create mode 100644 lib/pages/spaces_management/tag_model/widgets/device_icon_widget.dart create mode 100644 lib/pages/spaces_management/tag_model/widgets/device_name_widget.dart create mode 100644 lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart create mode 100644 lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart b/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart deleted file mode 100644 index 06ec6bf4..00000000 --- a/lib/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart +++ /dev/null @@ -1,202 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/pages/common/buttons/default_button.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/add_device_model_bloc.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/constants/assets.dart'; - -class AddDeviceTypeModelWidget extends StatelessWidget { - final List? products; - final ValueChanged>? onProductsSelected; - final List? initialSelectedProducts; - - const AddDeviceTypeModelWidget({ - super.key, - this.products, - this.initialSelectedProducts, - this.onProductsSelected, - }); - - @override - Widget build(BuildContext context) { - final size = MediaQuery.of(context).size; - final crossAxisCount = size.width > 1200 - ? 8 - : size.width > 800 - ? 5 - : 3; - - return BlocProvider( - create: (_) => AddDeviceTypeModelBloc( - initialSelectedProducts ?? []), // Initialize with initial products - child: AlertDialog( - title: const Text('Add Devices'), - backgroundColor: ColorsManager.whiteColors, - content: SingleChildScrollView( - child: Container( - width: size.width * 0.9, - height: size.height * 0.65, - color: ColorsManager.textFieldGreyColor, - child: Column( - children: [ - const SizedBox(height: 16), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: Scrollbar( - thumbVisibility: false, - child: BlocBuilder>( - builder: (context, productCounts) { - return GridView.builder( - shrinkWrap: true, - gridDelegate: - SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: crossAxisCount, - mainAxisSpacing: 6, - crossAxisSpacing: 4, - childAspectRatio: .8, - ), - itemCount: products?.length ?? 0, - itemBuilder: (context, index) { - final product = products![index]; - return _buildDeviceTypeTile( - context, product, size, productCounts); - }, - ); - }, - ), - ), - ), - ), - ], - ), - ), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _buildActionButton( - 'Cancel', - ColorsManager.boxColor, - ColorsManager.blackColor, - () => Navigator.of(context).pop(), - ), - _buildActionButton( - 'Continue', - ColorsManager.secondaryColor, - ColorsManager.whiteColors, - () { - Navigator.of(context).pop(); - if (onProductsSelected != null) { - final selectedProducts = - context.read().state; - onProductsSelected!(selectedProducts); - } - }, - ), - ], - ), - ], - ), - ); - } - - Widget _buildDeviceTypeTile(BuildContext context, ProductModel product, - Size size, List productCounts) { - final selectedProduct = productCounts.firstWhere( - (p) => p.productId == product.uuid, - orElse: () => SelectedProduct(productId: product.uuid, count: 0), - ); - - return SizedBox( - width: size.width * 0.12, - height: size.height * 0.15, - child: Card( - elevation: 2, - color: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - child: Padding( - padding: const EdgeInsets.all(4.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - _buildDeviceIcon(product, size), - const SizedBox(height: 4), - _buildDeviceName(context,product, size), - const SizedBox(height: 4), - CounterWidget( - initialCount: selectedProduct.count, - onCountChanged: (newCount) { - context - .read() - .updateProductCount(product.uuid, newCount); - }, - ), - ], - ), - ), - ), - ); - } - - Widget _buildDeviceIcon(ProductModel product, Size size) { - return Container( - height: size.width > 800 ? 50 : 40, - width: size.width > 800 ? 50 : 40, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: ColorsManager.textFieldGreyColor, - border: Border.all( - color: ColorsManager.neutralGray, - width: 2, - ), - ), - child: Center( - child: SvgPicture.asset( - product.icon ?? Assets.sensors, - width: size.width > 800 ? 30 : 20, - height: size.width > 800 ? 30 : 20, - ), - ), - ); - } - - Widget _buildDeviceName(BuildContext context, product, Size size) { - return SizedBox( - height: size.width > 800 ? 35 : 25, - child: Text( - product.name ?? '', - style: Theme.of(context).textTheme.bodySmall - ?.copyWith(color: ColorsManager.blackColor), - textAlign: TextAlign.center, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ); - } - - Widget _buildActionButton( - String label, - Color backgroundColor, - Color foregroundColor, - VoidCallback onPressed, - ) { - return SizedBox( - width: 120, - child: DefaultButton( - onPressed: onPressed, - backgroundColor: backgroundColor, - foregroundColor: foregroundColor, - child: Text(label), - ), - ); - } -} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index e2095703..a84b6409 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -7,7 +7,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_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/button_content_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/add_device_type_model_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; diff --git a/lib/pages/spaces_management/space_model/bloc/add_device_model_bloc.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart similarity index 100% rename from lib/pages/spaces_management/space_model/bloc/add_device_model_bloc.dart rename to lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart new file mode 100644 index 00000000..ad71116a --- /dev/null +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class AddDeviceTypeModelWidget extends StatelessWidget { + final List? products; + final ValueChanged>? onProductsSelected; + final List? initialSelectedProducts; + + const AddDeviceTypeModelWidget({ + super.key, + this.products, + this.initialSelectedProducts, + this.onProductsSelected, + }); + + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + final crossAxisCount = size.width > 1200 + ? 8 + : size.width > 800 + ? 5 + : 3; + + return BlocProvider( + create: (_) => AddDeviceTypeModelBloc(initialSelectedProducts ?? []), + child: AlertDialog( + title: const Text('Add Devices'), + backgroundColor: ColorsManager.whiteColors, + content: SingleChildScrollView( + child: Container( + width: size.width * 0.9, + height: size.height * 0.65, + color: ColorsManager.textFieldGreyColor, + child: Column( + children: [ + const SizedBox(height: 16), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: ScrollableGridViewWidget( + products: products, + crossAxisCount: crossAxisCount, + ), + ), + ), + ], + ), + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ActionButton( + label: 'Cancel', + backgroundColor: ColorsManager.boxColor, + foregroundColor: ColorsManager.blackColor, + onPressed: () => Navigator.of(context).pop(), + ), + ActionButton( + label: 'Continue', + backgroundColor: ColorsManager.secondaryColor, + foregroundColor: ColorsManager.whiteColors, + onPressed: () { + Navigator.of(context).pop(); + if (onProductsSelected != null) { + final selectedProducts = + context.read().state; + onProductsSelected!(selectedProducts); + } + }, + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/tag_model/widgets/action_button_widget.dart b/lib/pages/spaces_management/tag_model/widgets/action_button_widget.dart new file mode 100644 index 00000000..8d10a4de --- /dev/null +++ b/lib/pages/spaces_management/tag_model/widgets/action_button_widget.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; + +class ActionButton extends StatelessWidget { + final String label; + final Color backgroundColor; + final Color foregroundColor; + final VoidCallback onPressed; + + const ActionButton({ + super.key, + required this.label, + required this.backgroundColor, + required this.foregroundColor, + required this.onPressed, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 120, + child: DefaultButton( + onPressed: onPressed, + backgroundColor: backgroundColor, + foregroundColor: foregroundColor, + child: Text(label), + ), + ); + } +} diff --git a/lib/pages/spaces_management/tag_model/widgets/device_icon_widget.dart b/lib/pages/spaces_management/tag_model/widgets/device_icon_widget.dart new file mode 100644 index 00000000..4e8adbd9 --- /dev/null +++ b/lib/pages/spaces_management/tag_model/widgets/device_icon_widget.dart @@ -0,0 +1,36 @@ +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 DeviceIconWidget extends StatelessWidget { + final String? icon; + + const DeviceIconWidget({ + super.key, + required this.icon, + }); + + @override + Widget build(BuildContext context) { + return Container( + height: 50, + width: 50, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.textFieldGreyColor, + border: Border.all( + color: ColorsManager.neutralGray, + width: 2, + ), + ), + child: Center( + child: SvgPicture.asset( + icon ?? Assets.sensors, + width: 30, + height: 30, + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/tag_model/widgets/device_name_widget.dart b/lib/pages/spaces_management/tag_model/widgets/device_name_widget.dart new file mode 100644 index 00000000..11a102a8 --- /dev/null +++ b/lib/pages/spaces_management/tag_model/widgets/device_name_widget.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DeviceNameWidget extends StatelessWidget { + final String? name; + + const DeviceNameWidget({ + super.key, + required this.name, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 35, + child: Text( + name ?? '', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.blackColor), + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ); + } +} diff --git a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart new file mode 100644 index 00000000..13631448 --- /dev/null +++ b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_icon_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_name_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class DeviceTypeTileWidget extends StatelessWidget { + final ProductModel product; + final List productCounts; + + const DeviceTypeTileWidget({ + super.key, + required this.product, + required this.productCounts, + }); + + @override + Widget build(BuildContext context) { + final selectedProduct = productCounts.firstWhere( + (p) => p.productId == product.uuid, + orElse: () => SelectedProduct(productId: product.uuid, count: 0), + ); + + return Card( + elevation: 2, + color: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.all(4.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + DeviceIconWidget(icon: product.icon ?? Assets.doorSensor), + const SizedBox(height: 4), + DeviceNameWidget(name: product.name), + const SizedBox(height: 4), + CounterWidget( + initialCount: selectedProduct.count, + onCountChanged: (newCount) { + context + .read() + .updateProductCount(product.uuid, newCount); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart b/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart new file mode 100644 index 00000000..e05c6711 --- /dev/null +++ b/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart'; + +class ScrollableGridViewWidget extends StatelessWidget { + final List? products; + final int crossAxisCount; + + const ScrollableGridViewWidget({ + super.key, + required this.products, + required this.crossAxisCount, + }); + + @override + Widget build(BuildContext context) { + final scrollController = ScrollController(); + + return Scrollbar( + controller: scrollController, + thumbVisibility: true, + child: BlocBuilder>( + builder: (context, productCounts) { + return GridView.builder( + controller: scrollController, + shrinkWrap: true, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + mainAxisSpacing: 6, + crossAxisSpacing: 4, + childAspectRatio: .8, + ), + itemCount: products?.length ?? 0, + itemBuilder: (context, index) { + final product = products![index]; + return DeviceTypeTileWidget( + product: product, + productCounts: productCounts, + ); + }, + ); + }, + ), + ); + } +} From 58b469b92af0c05737695323febc90d48b2cddc4 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 16:15:35 +0400 Subject: [PATCH 047/175] refactor --- .../tag_model/bloc/add_device_model_bloc.dart | 28 ++++++++++++------- .../bloc/add_device_type_model_event.dart | 16 +++++++++++ .../widgets/device_type_tile_widget.dart | 7 +++-- 3 files changed, 38 insertions(+), 13 deletions(-) create mode 100644 lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart index 31c99dc4..1352e97c 100644 --- a/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart @@ -1,30 +1,38 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart'; -class AddDeviceTypeModelBloc extends Cubit> { +class AddDeviceTypeModelBloc + extends Bloc> { AddDeviceTypeModelBloc(List initialProducts) - : super(initialProducts); + : super(initialProducts) { + on(_onUpdateProductCount); + } - void updateProductCount(String productId, int count) { + void _onUpdateProductCount( + UpdateProductCountEvent event, Emitter> emit) { final existingProduct = state.firstWhere( - (p) => p.productId == productId, - orElse: () => SelectedProduct(productId: productId, count: 0), + (p) => p.productId == event.productId, + orElse: () => SelectedProduct(productId: event.productId, count: 0), ); - if (count > 0) { + if (event.count > 0) { if (!state.contains(existingProduct)) { - emit([...state, SelectedProduct(productId: productId, count: count)]); + emit([ + ...state, + SelectedProduct(productId: event.productId, count: event.count) + ]); } else { final updatedList = state.map((p) { - if (p.productId == productId) { - return SelectedProduct(productId: p.productId, count: count); + if (p.productId == event.productId) { + return SelectedProduct(productId: p.productId, count: event.count); } return p; }).toList(); emit(updatedList); } } else { - emit(state.where((p) => p.productId != productId).toList()); + emit(state.where((p) => p.productId != event.productId).toList()); } } } diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart new file mode 100644 index 00000000..a3feaad8 --- /dev/null +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart @@ -0,0 +1,16 @@ +import 'package:equatable/equatable.dart'; + +abstract class AddDeviceTypeModelEvent extends Equatable { + @override + List get props => []; +} + +class UpdateProductCountEvent extends AddDeviceTypeModelEvent { + final String productId; + final int count; + + UpdateProductCountEvent({required this.productId, required this.count}); + + @override + List get props => [productId, count]; +} diff --git a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart index 13631448..1190bc5c 100644 --- a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart +++ b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart @@ -45,9 +45,10 @@ class DeviceTypeTileWidget extends StatelessWidget { CounterWidget( initialCount: selectedProduct.count, onCountChanged: (newCount) { - context - .read() - .updateProductCount(product.uuid, newCount); + context.read().add( + UpdateProductCountEvent( + productId: product.uuid, count: newCount), + ); }, ), ], From 3876909beade9c20ef85842367554c1e8547256b Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 5 Jan 2025 17:27:26 +0300 Subject: [PATCH 048/175] edit_user and pagination and search and filter --- .../model/edit_user_model.dart | 267 +++++++++++++ .../model/roles_user_model.dart | 68 +++- .../add_user_dialog/bloc/users_bloc.dart | 232 ++++++++++- .../add_user_dialog/bloc/users_event.dart | 90 ++++- .../add_user_dialog/bloc/users_status.dart | 8 + .../add_user_dialog/view/add_user_dialog.dart | 15 +- .../add_user_dialog/view/basics_view.dart | 8 +- .../add_user_dialog/view/build_tree_view.dart | 146 +++++++ .../view/delete_user_dialog.dart | 129 +++++-- .../view/edit_user_dialog.dart | 364 ++++++++++++++++++ .../view/popup_menu_filter.dart | 108 ++++++ .../view/spaces_access_view.dart | 161 +------- .../users_table/bloc/user_table_bloc.dart | 259 +++++++++---- .../users_table/bloc/user_table_event.dart | 62 ++- .../users_table/view/user_table.dart | 136 ++++--- .../users_table/view/users_page.dart | 245 ++++++++++-- .../view/roles_and_permission_page.dart | 2 +- lib/services/space_mana_api.dart | 1 + lib/services/user_permission.dart | 114 ++++++ lib/utils/constants/api_const.dart | 8 +- macos/Podfile.lock | 36 ++ macos/Runner.xcodeproj/project.pbxproj | 98 ++++- .../contents.xcworkspacedata | 3 + pubspec.lock | 8 + pubspec.yaml | 1 + 25 files changed, 2156 insertions(+), 413 deletions(-) create mode 100644 lib/pages/roles_and_permission/model/edit_user_model.dart create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart create mode 100644 lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart create mode 100644 macos/Podfile.lock diff --git a/lib/pages/roles_and_permission/model/edit_user_model.dart b/lib/pages/roles_and_permission/model/edit_user_model.dart new file mode 100644 index 00000000..17fba1a4 --- /dev/null +++ b/lib/pages/roles_and_permission/model/edit_user_model.dart @@ -0,0 +1,267 @@ +// import 'dart:convert'; + +// // Model for Space +// class UserSpaceModel { +// final String uuid; +// final DateTime createdAt; +// final DateTime updatedAt; + +// UserSpaceModel({ +// required this.uuid, +// required this.createdAt, +// required this.updatedAt, +// }); + +// factory UserSpaceModel.fromJson(Map json) { +// return UserSpaceModel( +// uuid: json['uuid'], +// createdAt: DateTime.parse(json['createdAt']), +// updatedAt: DateTime.parse(json['updatedAt']), +// ); +// } + +// Map toJson() { +// return { +// 'uuid': uuid, +// 'createdAt': createdAt.toIso8601String(), +// 'updatedAt': updatedAt.toIso8601String(), +// }; +// } +// } + +// // Model for User +// class EditUserModel { +// final String uuid; +// final DateTime createdAt; +// final dynamic email; +// final dynamic? jobTitle; +// final dynamic status; +// final String firstName; +// final String lastName; +// final String? phoneNumber; +// final bool isEnabled; +// final dynamic invitedBy; +// final dynamic roleType; +// final List spaces; +// final String createdDate; +// final String createdTime; + +// EditUserModel({ +// required this.uuid, +// required this.createdAt, +// required this.email, +// this.jobTitle, +// required this.status, +// required this.firstName, +// required this.lastName, +// this.phoneNumber, +// required this.isEnabled, +// required this.invitedBy, +// required this.roleType, +// required this.spaces, +// required this.createdDate, +// required this.createdTime, +// }); + +// factory EditUserModel.fromJson(Map json) { +// var spacesList = (json['spaces'] as List) +// .map((spaceJson) => UserSpaceModel.fromJson(spaceJson)) +// .toList(); + +// return EditUserModel( +// uuid: json['uuid'], +// createdAt: DateTime.parse(json['createdAt']), +// email: json['email'], +// jobTitle: json['jobTitle'], +// status: json['status'], +// firstName: json['firstName'], +// lastName: json['lastName'], +// phoneNumber: json['phoneNumber'], +// isEnabled: json['isEnabled'], +// invitedBy: json['invitedBy'], +// roleType: json['roleType'], +// spaces: spacesList, +// createdDate: json['createdDate'], +// createdTime: json['createdTime'], +// ); +// } + +// Map toJson() { +// return { +// 'uuid': uuid, +// 'createdAt': createdAt.toIso8601String(), +// 'email': email, +// 'jobTitle': jobTitle, +// 'status': status, +// 'firstName': firstName, +// 'lastName': lastName, +// 'phoneNumber': phoneNumber, +// 'isEnabled': isEnabled, +// 'invitedBy': invitedBy, +// 'roleType': roleType, +// 'spaces': spaces.map((space) => space.toJson()).toList(), +// 'createdDate': createdDate, +// 'createdTime': createdTime, +// }; +// } +// } + +class UserProjectResponse { + final int statusCode; + final String message; + final EditUserModel data; + final bool success; + + UserProjectResponse({ + required this.statusCode, + required this.message, + required this.data, + required this.success, + }); + + /// Create a [UserProjectResponse] from JSON data + factory UserProjectResponse.fromJson(Map json) { + return UserProjectResponse( + statusCode: json['statusCode'] as int, + message: json['message'] as String, + data: EditUserModel.fromJson(json['data'] as Map), + success: json['success'] as bool, + ); + } + + /// Convert the [UserProjectResponse] to JSON + Map toJson() { + return { + 'statusCode': statusCode, + 'message': message, + 'data': data.toJson(), + 'success': success, + }; + } +} + +class EditUserModel { + final String uuid; + final String firstName; + final String lastName; + final String email; + final String createdDate; // e.g. "1/3/2025" + final String createdTime; // e.g. "8:41:43 AM" + final String status; // e.g. "invited" + final String invitedBy; // e.g. "SUPER_ADMIN" + final String phoneNumber; // can be empty + final String jobTitle; // can be empty + final String roleType; // e.g. "ADMIN" + final List spaces; + + EditUserModel({ + required this.uuid, + required this.firstName, + required this.lastName, + required this.email, + required this.createdDate, + required this.createdTime, + required this.status, + required this.invitedBy, + required this.phoneNumber, + required this.jobTitle, + required this.roleType, + required this.spaces, + }); + + /// Create a [UserData] from JSON data + factory EditUserModel.fromJson(Map json) { + return EditUserModel( + uuid: json['uuid'] as String, + firstName: json['firstName'] as String, + lastName: json['lastName'] as String, + email: json['email'] as String, + createdDate: json['createdDate'] as String, + createdTime: json['createdTime'] as String, + status: json['status'] as String, + invitedBy: json['invitedBy'] as String, + phoneNumber: json['phoneNumber'] ?? '', + jobTitle: json['jobTitle'] ?? '', + roleType: json['roleType'] as String, + spaces: (json['spaces'] as List) + .map((e) => UserSpaceModel.fromJson(e as Map)) + .toList(), + ); + } + + /// Convert the [UserData] to JSON + Map toJson() { + return { + 'uuid': uuid, + 'firstName': firstName, + 'lastName': lastName, + 'email': email, + 'createdDate': createdDate, + 'createdTime': createdTime, + 'status': status, + 'invitedBy': invitedBy, + 'phoneNumber': phoneNumber, + 'jobTitle': jobTitle, + 'roleType': roleType, + 'spaces': spaces.map((e) => e.toJson()).toList(), + }; + } +} + +class UserSpaceModel { + final String uuid; + final String createdAt; // e.g. "2024-11-04T07:20:35.940Z" + final String updatedAt; // e.g. "2024-11-28T18:47:29.736Z" + final dynamic spaceTuyaUuid; + final dynamic spaceName; + final dynamic invitationCode; + final bool disabled; + final double x; + final double y; + final String icon; + + UserSpaceModel({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.spaceTuyaUuid, + required this.spaceName, + required this.invitationCode, + required this.disabled, + required this.x, + required this.y, + required this.icon, + }); + + /// Create a [UserSpaceModel] from JSON data + factory UserSpaceModel.fromJson(Map json) { + return UserSpaceModel( + uuid: json['uuid'] as String, + createdAt: json['createdAt'] as String, + updatedAt: json['updatedAt'] as String, + spaceTuyaUuid: json['spaceTuyaUuid'] as String?, + spaceName: json['spaceName'] as String, + invitationCode: json['invitationCode'] as String?, + disabled: json['disabled'] as bool, + x: (json['x'] as num).toDouble(), + y: (json['y'] as num).toDouble(), + icon: json['icon'] as String, + ); + } + + /// Convert the [UserSpaceModel] to JSON + Map toJson() { + return { + 'uuid': uuid, + 'createdAt': createdAt, + 'updatedAt': updatedAt, + 'spaceTuyaUuid': spaceTuyaUuid, + 'spaceName': spaceName, + 'invitationCode': invitationCode, + 'disabled': disabled, + 'x': x, + 'y': y, + 'icon': icon, + }; + } +} diff --git a/lib/pages/roles_and_permission/model/roles_user_model.dart b/lib/pages/roles_and_permission/model/roles_user_model.dart index 244c58de..6298dbe6 100644 --- a/lib/pages/roles_and_permission/model/roles_user_model.dart +++ b/lib/pages/roles_and_permission/model/roles_user_model.dart @@ -1,22 +1,50 @@ class RolesUserModel { - String? id; - String? userName; - String? userEmail; - String? userRole; - String? creationDate; - String? creationTime; - String? createdBy; - String? status; - String? action; - RolesUserModel( - {this.id, - this.userName, - this.userEmail, - this.userRole, - this.creationDate, - this.creationTime, - this.status, - this.action, - this.createdBy, - }); + final String uuid; + final DateTime createdAt; + final String email; + final dynamic firstName; + final dynamic lastName; + final dynamic roleType; + final dynamic status; + final bool isEnabled; + final String invitedBy; + final dynamic phoneNumber; + final dynamic jobTitle; + final dynamic createdDate; + final dynamic createdTime; + + RolesUserModel({ + required this.uuid, + required this.createdAt, + required this.email, + required this.firstName, + required this.lastName, + required this.roleType, + required this.status, + required this.isEnabled, + required this.invitedBy, + this.phoneNumber, + this.jobTitle, + required this.createdDate, + required this.createdTime, + }); + + factory RolesUserModel.fromJson(Map json) { + return RolesUserModel( + uuid: json['uuid'], + createdAt: DateTime.parse(json['createdAt']), + email: json['email'], + firstName: json['firstName'], + lastName: json['lastName'], + roleType: json['roleType'].toString().toLowerCase().replaceAll("_", " "), + status: json['status'], + isEnabled: json['isEnabled'], + invitedBy: + json['invitedBy'].toString().toLowerCase().replaceAll("_", " "), + phoneNumber: json['phoneNumber'], + jobTitle: json['jobTitle'].toString(), + createdDate: json['createdDate'], + createdTime: json['createdTime'], + ); + } } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index dbace8ad..730825cb 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -1,6 +1,7 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/common/custom_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/edit_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; @@ -25,6 +26,11 @@ class UsersBloc extends Bloc { on(_validateBasicsStep); on(isCompleteRoleFun); on(checkEmail); + on(getUserById); + on(_onToggleNodeExpansion); + on(_onToggleNodeCheck); + on(_editInvitUser); + } void _validateBasicsStep(ValidateBasicsStep event, Emitter emit) { if (formKey.currentState?.validate() ?? false) { @@ -42,9 +48,7 @@ class UsersBloc extends Bloc { final TextEditingController emailController = TextEditingController(); final TextEditingController phoneController = TextEditingController(); final TextEditingController jobTitleController = TextEditingController(); - final TextEditingController roleSearchController = TextEditingController(); - // final TextEditingController jobTitleController = TextEditingController(); bool? isCompleteBasics; bool? isCompleteRolePermissions; @@ -84,6 +88,7 @@ class UsersBloc extends Bloc { await CommunitySpaceManagementApi().fetchCommunities(); updatedCommunities = await Future.wait( communities.map((community) async { + print(community.uuid); List spaces = await _fetchSpacesForCommunity(community.uuid); spacesNodes = _buildTreeNodes(spaces); @@ -228,8 +233,8 @@ class UsersBloc extends Bloc { actions: [ TextButton( onPressed: () { - Navigator.of(event.context).pop(); - Navigator.of(event.context).pop(); + Navigator.of(event.context).pop(true); + Navigator.of(event.context).pop(true); }, child: const Text('OK'), ), @@ -244,6 +249,48 @@ class UsersBloc extends Bloc { } } + _editInvitUser(EditInviteUsers event, Emitter emit) async { + try { + emit(UsersLoadingState()); + List selectedIds = getSelectedIds(updatedCommunities) ?? []; + bool res = await UserPermissionApi().editInviteUser( + userId: event.userId, + firstName: firstNameController.text, + jobTitle: jobTitleController.text, + lastName: lastNameController.text, + phoneNumber: phoneController.text, + roleUuid: roleSelected, + spaceUuids: selectedIds, + ); + if (res == true) { + showCustomDialog( + barrierDismissible: false, + context: event.context, + message: "The invite was sent successfully.", + iconPath: Assets.deviceNoteIcon, + title: "Invite Success", + dialogHeight: MediaQuery.of(event.context).size.height * 0.3, + actions: [ + TextButton( + onPressed: () { + Navigator.of(event.context).pop(true); + Navigator.of(event.context).pop(true); + }, + child: const Text('OK'), + ), + ], + ).then( + (value) {}, + ); + } else { + emit(const ErrorState('Failed to send invite.')); + } + emit(SaveState()); + } catch (e) { + emit(ErrorState('Failed to send invite: ${e.toString()}')); + } + } + void searchRolePermission(SearchPermission event, Emitter emit) { emit(UsersLoadingState()); if (event.searchTerm!.isEmpty) { @@ -268,18 +315,22 @@ class UsersBloc extends Bloc { bool isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { emit(UsersLoadingState()); - add(const CheckEmailEvent()); - final emailRegex = RegExp( - r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', - ); - bool isEmailValid = emailRegex.hasMatch(emailController.text); - bool isEmailServerValid = checkEmailValid == 'Valid email'; - isCompleteBasics = firstNameController.text.isNotEmpty && - lastNameController.text.isNotEmpty && - emailController.text.isNotEmpty && - isEmailValid && - isEmailServerValid; - + if (event.isEditUser == false) { + add(const CheckEmailEvent()); + final emailRegex = RegExp( + r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', + ); + bool isEmailValid = emailRegex.hasMatch(emailController.text); + bool isEmailServerValid = checkEmailValid == 'Valid email'; + isCompleteBasics = firstNameController.text.isNotEmpty && + lastNameController.text.isNotEmpty && + emailController.text.isNotEmpty && + isEmailValid && + isEmailServerValid; + } else { + isCompleteBasics = firstNameController.text.isNotEmpty && + lastNameController.text.isNotEmpty; + } emit(ChangeStatusSteps()); emit(ValidateBasics()); return isCompleteBasics!; @@ -293,4 +344,153 @@ class UsersBloc extends Bloc { } } } + + EditUserModel? res = EditUserModel( + spaces: [], + jobTitle: '', + phoneNumber: '', + uuid: '', + email: '', + firstName: '', + lastName: '', + roleType: '', + status: '', + invitedBy: '', + createdDate: '', + createdTime: ''); + + Future getUserById( + GetUserByIdEvent event, + Emitter emit, + ) async { + emit(UsersLoadingState()); + + try { + if (event.uuid?.isNotEmpty ?? false) { + final res = await UserPermissionApi().fetchUserById(event.uuid); + + if (res != null) { + // Populate the text controllers + firstNameController.text = res.firstName; + lastNameController.text = res.lastName; + emailController.text = res.email; + phoneController.text = res.phoneNumber ?? ''; + jobTitleController.text = res.jobTitle ?? ''; + res.roleType; + if (updatedCommunities.isNotEmpty) { + // Create a list of UUIDs to mark + final uuidsToMark = res.spaces.map((space) => space.uuid).toList(); + // Print all IDs and mark nodes in updatedCommunities + print('Printing and marking nodes in updatedCommunities:'); + _printAndMarkNodes(updatedCommunities, uuidsToMark); + } else { + print('updatedCommunities is empty!'); + } + final roleId = roles + .firstWhere((element) => + element.type == + res.roleType.toString().toLowerCase().replaceAll("_", " ")) + .uuid; + print('Role ID: $roleId'); + roleSelected = roleId; + add(PermissionEvent(roleUuid: roleSelected)); + emit(ChangeStatusSteps()); + } else { + // emit(UsersErrorState("User not found")); + } + } else { + // emit(UsersErrorState("Invalid user ID")); + } + } catch (e) { + print("Failed to fetch user data: $e"); + // emit(UsersErrorState("Failed to fetch user data: $e")); + } + } + + /// Recursively print all the node IDs, including nested children. + /// Recursively print all node IDs and mark nodes as `isChecked` if their UUID exists in the list. + void _printAndMarkNodes(List nodes, List uuidsToMark, + [int level = 0]) { + for (final node in nodes) { + // Check if the current node's UUID exists in the list of UUIDs to mark. + if (uuidsToMark.contains(node.uuid)) { + node.isChecked = true; // Mark the node as checked. + print( + '${' ' * level}MATCH FOUND: Node ID: ${node.uuid}, Title: ${node.title} is marked as checked.'); + } else { + print('${' ' * level}Node ID: ${node.uuid}, Title: ${node.title}'); + } + if (node.children.isNotEmpty) { + _printAndMarkNodes(node.children, uuidsToMark, level + 1); + } + } + } + + void _onToggleNodeExpansion( + ToggleNodeExpansion event, + Emitter emit, + ) { + emit(UsersLoadingState()); + event.node.isExpanded = !event.node.isExpanded; + emit(ChangeStatusSteps()); + } + + void _onToggleNodeCheck( + ToggleNodeCheck event, + Emitter emit, + ) { + emit(UsersLoadingState()); + //Toggle node's checked state + event.node.isChecked = !event.node.isChecked; + debugPrint( + 'Node toggled. ID: ${event.node.uuid}, isChecked: ${event.node.isChecked}', + ); + // Update children and parent + _updateChildrenCheckStatus(event.node, event.node.isChecked); + _updateParentCheckStatus(event.node); + + // Finally, emit a new state + emit(ChangeStatusSteps()); + } + +// Existing methods that remain in the BLoC: + + void _updateChildrenCheckStatus(TreeNode node, bool isChecked) { + for (var child in node.children) { + child.isChecked = isChecked; + _updateChildrenCheckStatus(child, isChecked); + } + } + + void _updateParentCheckStatus(TreeNode node) { + TreeNode? parent = _findParent(updatedCommunities, node); + if (parent != null) { + parent.isChecked = _areAllChildrenChecked(parent); + _updateParentCheckStatus(parent); + } + } + + bool _areAllChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.every((child) => + child.isChecked && + (child.children.isEmpty || _areAllChildrenChecked(child))); + } + +// Private helper method to find the parent of a given node. + TreeNode? _findParent(List nodes, TreeNode target) { + for (var node in nodes) { + if (node.children.contains(target)) { + return node; + } + final parent = _findParent(node.children, target); + if (parent != null) { + return parent; + } + } + return null; + } + + + } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart index 950726d4..0f4631ff 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart @@ -14,6 +14,14 @@ class SendInviteUsers extends UsersEvent { List get props => [context]; } +class EditInviteUsers extends UsersEvent { + final BuildContext context; + final String userId; + const EditInviteUsers({required this.context, required this.userId}); + @override + List get props => [context, userId]; +} + class CheckSpacesStepStatus extends UsersEvent { const CheckSpacesStepStatus(); @override @@ -52,9 +60,11 @@ class GetBatchStatus extends UsersEvent { List get props => [uuids]; } +//isEditUser:widget.userId!=''? false:true class CheckStepStatus extends UsersEvent { final int? steps; - const CheckStepStatus({this.steps}); + bool? isEditUser = false; + CheckStepStatus({this.steps, required this.isEditUser}); @override List get props => [steps]; } @@ -85,7 +95,7 @@ class SelecteId extends UsersEvent { } class ValidateBasicsStep extends UsersEvent { - const ValidateBasicsStep(); + ValidateBasicsStep(); @override List get props => []; } @@ -95,3 +105,79 @@ class CheckEmailEvent extends UsersEvent { @override List get props => []; } + +class GetUserByIdEvent extends UsersEvent { + final String? uuid; + const GetUserByIdEvent({this.uuid}); + @override + List get props => [uuid]; +} + +class ToggleNodeExpansion extends UsersEvent { + final TreeNode node; + + ToggleNodeExpansion({required this.node}); + + @override + List get props => [node]; +} + +class UpdateNodeCheckStatus extends UsersEvent { + final TreeNode node; + + UpdateNodeCheckStatus({required this.node}); + @override + List get props => [node]; +} + +// Define new events +class ToggleNodeHighlightEvent extends UsersEvent { + final TreeNode node; + + ToggleNodeHighlightEvent(this.node); + @override + List get props => [node]; +} + +class ExpandAllNodesEvent extends UsersEvent { + @override + List get props => []; +} + +class CollapseAllNodesEvent extends UsersEvent { + @override + List get props => []; +} + +class ClearSelectionsEvent extends UsersEvent { + @override + List get props => []; +} + +class ToggleNodeCheckEvent extends UsersEvent { + final TreeNode node; + + ToggleNodeCheckEvent(this.node); + @override + List get props => []; +} + +// users_event.dart + +// 1. Extend UsersEvent +class ToggleNodeCheck extends UsersEvent { + final TreeNode node; + + // 2. Add a constructor that takes the node to toggle + ToggleNodeCheck(this.node); + @override + List get props => []; +} + +class EditUserEvent extends UsersEvent { + const EditUserEvent(); + @override + List get props => []; +} + + diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart index c1bf3512..646dccfd 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; sealed class UsersState extends Equatable { const UsersState(); @@ -80,3 +81,10 @@ final class ValidateBasics extends UsersState { @override List get props => []; } +class UsersLoadedState extends UsersState { + final List updatedCommunities; + + UsersLoadedState({required this.updatedCommunities}); + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart index 14f7a0fb..48df801a 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart @@ -113,7 +113,8 @@ class _AddNewUserDialogState extends State { if (currentStep < 3) { currentStep++; if (currentStep == 2) { - _blocRole.add(const CheckStepStatus()); + _blocRole.add( + CheckStepStatus(isEditUser: false)); } else if (currentStep == 3) { _blocRole .add(const CheckSpacesStepStatus()); @@ -150,9 +151,11 @@ class _AddNewUserDialogState extends State { Widget _getFormContent() { switch (currentStep) { case 1: - return const BasicsView(); + return BasicsView( + userId: '', + ); case 2: - return const SpacesAccessView(); + return SpacesAccessView(); case 3: return const RolesAndPermission(); default: @@ -169,7 +172,7 @@ class _AddNewUserDialogState extends State { bloc.add(const CheckSpacesStepStatus()); currentStep = step; Future.delayed(const Duration(milliseconds: 500), () { - bloc.add(const ValidateBasicsStep()); + bloc.add(ValidateBasicsStep()); }); }); @@ -234,7 +237,7 @@ class _AddNewUserDialogState extends State { onTap: () { setState(() { currentStep = step; - bloc.add(const CheckStepStatus()); + bloc.add(CheckStepStatus(isEditUser: false)); if (step3 == 3) { bloc.add(const CheckRoleStepStatus()); } @@ -299,7 +302,7 @@ class _AddNewUserDialogState extends State { currentStep = step; step3 = step; bloc.add(const CheckSpacesStepStatus()); - bloc.add(const CheckStepStatus()); + bloc.add(CheckStepStatus(isEditUser: false)); }); }, child: Column( diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart index a6ec686b..bbca9aaa 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart @@ -11,7 +11,8 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class BasicsView extends StatelessWidget { - const BasicsView({super.key}); + String? userId = ''; + BasicsView({super.key, this.userId}); @override Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { @@ -184,10 +185,11 @@ class BasicsView extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( + enabled: userId!=''? false:true, onChanged: (value) { Future.delayed(const Duration(milliseconds: 200), () { - _blocRole.add(const CheckStepStatus()); - _blocRole.add(ValidateBasicsStep()); + _blocRole.add(CheckStepStatus(isEditUser:userId!=''? false:true)); + _blocRole.add( ValidateBasicsStep()); }); }, controller: _blocRole.emailController, 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 new file mode 100644 index 00000000..b7fc1085 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart @@ -0,0 +1,146 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class TreeView extends StatelessWidget { + final String? userId; + + const TreeView({ + super.key, + this.userId, + }); + + @override + Widget build(BuildContext context) { + final _blocRole = BlocProvider.of(context); + debugPrint('TreeView constructed with userId = $userId'); + return BlocProvider( + create: (_) => UsersBloc(), + // ..add(const LoadCommunityAndSpacesEvent()), + child: BlocConsumer( + listener: (context, state) { + // if (state is SpacesLoadedState) { + // _blocRole.add(GetUserByIdEvent(uuid: userId)); + // } + }, + builder: (context, state) { + if (state is UsersLoadingState) { + return const Center(child: CircularProgressIndicator()); + } + return SingleChildScrollView( + child: _buildTree(_blocRole.updatedCommunities, _blocRole), + ); + }, + ), + ); + } + + Widget _buildTree( + List nodes, + UsersBloc bloc, { + int level = 0, + }) { + return Column( + children: nodes.map((node) { + return Container( + color: node.isHighlighted ? Colors.blue.shade50 : Colors.transparent, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + /// Checkbox (GestureDetector) + GestureDetector( + onTap: () { + bloc.add(ToggleNodeCheck(node)); + }, + child: Image.asset( + _getCheckBoxImage(node), + width: 20, + height: 20, + ), + ), + const SizedBox(width: 15), + Expanded( + child: Padding( + padding: EdgeInsets.only(left: level * 10.0), + child: Row( + children: [ + GestureDetector( + onTap: () { + bloc.add(ToggleNodeExpansion(node: node)); + }, + child: node.children.isNotEmpty + ? SvgPicture.asset( + node.isExpanded + ? Assets.arrowDown + : Assets.arrowForward, + fit: BoxFit.none, + ) + : const SizedBox(width: 16), + ), + const SizedBox(width: 20), + Text( + node.title, + style: TextStyle( + fontSize: 16, + color: node.isHighlighted + ? ColorsManager.blackColor + : ColorsManager.textGray, + ), + ), + ], + ), + ), + ), + ], + ), + ), + if (node.isExpanded) + _buildTree( + node.children, + bloc, + level: level + 1, + ), + ], + ), + ); + }).toList(), + ); + } + + String _getCheckBoxImage(TreeNode node) { + if (node.children.isEmpty) { + return node.isChecked ? Assets.CheckBoxChecked : Assets.emptyBox; + } + if (_areAllChildrenChecked(node)) { + return Assets.CheckBoxChecked; + } else if (_areSomeChildrenChecked(node)) { + return Assets.rectangleCheckBox; + } else { + return Assets.emptyBox; + } + } + + bool _areAllChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.every((child) => + child.isChecked && + (child.children.isEmpty || _areAllChildrenChecked(child))); + } + + bool _areSomeChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.any((child) => + child.isChecked || + (child.children.isNotEmpty && _areSomeChildrenChecked(child))); + } +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart index 002b0171..837ee62c 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class DeleteUserDialog extends StatefulWidget { - const DeleteUserDialog({super.key}); + final Function()? onTapDelete; + DeleteUserDialog({super.key, this.onTapDelete}); @override _DeleteUserDialogState createState() => _DeleteUserDialogState(); @@ -17,47 +15,94 @@ class _DeleteUserDialogState extends State { @override Widget build(BuildContext context) { - return BlocProvider( - create: (BuildContext context) => UsersBloc(), - child: BlocConsumer( - listener: (context, state) {}, - builder: (context, state) { - final _blocRole = BlocProvider.of(context); - - return Dialog( - child: Container( - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(20))), - child: const Column( - children: [ - Padding( - padding: EdgeInsets.all(8.0), - child: SizedBox( - child: Text( - "Delete User", - style: TextStyle( - color: ColorsManager.red, - fontSize: 18, - fontWeight: FontWeight.bold), + return Dialog( + child: Container( + height: 160, + width: 200, + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(20))), + child: Column( + children: [ + const Padding( + padding: EdgeInsets.all(8.0), + child: SizedBox( + child: Text( + "Delete User", + style: TextStyle( + color: ColorsManager.red, + fontSize: 18, + fontWeight: FontWeight.bold), + ), + ), + ), + const Padding( + padding: EdgeInsets.only( + left: 25, + right: 25, + ), + child: Divider(), + ), + const Expanded( + child: Padding( + padding: EdgeInsets.only(left: 25, right: 25, top: 10, bottom: 10), + child: Text( + "Are you sure you want to delete this user?", + textAlign: TextAlign.center, + ), + )), + Row( + children: [ + Expanded( + child: InkWell( + onTap: () { + Navigator.of(context).pop(true); + }, + child: Container( + padding: const EdgeInsets.all(10), + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.grayBorder, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.grayBorder, + width: 1, ), ), ), - Divider(), - Expanded( + child: const Center(child: Text('Cancel'))), + )), + Expanded( + child: InkWell( + onTap: widget.onTapDelete, + child: Container( + padding: const EdgeInsets.all(10), + decoration: const BoxDecoration( + border: Border( + left: BorderSide( + color: ColorsManager.grayBorder, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.grayBorder, + width: 1, + ), + ), + ), + child: const Center( child: Text( - "Are you sure you want to delete this user?", - textAlign: TextAlign.center, - )), - Row( - children: [ - Expanded(child: Text('Cancel')), - Expanded(child: Text('Delete')), - ], - ) - ], - ), - )); - })); + 'Delete', + style: TextStyle( + color: ColorsManager.red, + ), + ))), + )), + ], + ) + ], + ), + )); } } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart new file mode 100644 index 00000000..8b6600e0 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart @@ -0,0 +1,364 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class EditUserDialog extends StatefulWidget { + final String? userId; + const EditUserDialog({super.key, this.userId}); + + @override + _EditUserDialogState createState() => _EditUserDialogState(); +} + +class _EditUserDialogState extends State { + int currentStep = 1; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => UsersBloc() + ..add(const LoadCommunityAndSpacesEvent()) + ..add(const RoleEvent()) + ..add(GetUserByIdEvent(uuid: widget.userId)), + child: BlocConsumer(listener: (context, state) { + if (state is SpacesLoadedState) { + BlocProvider.of(context) + .add(GetUserByIdEvent(uuid: widget.userId)); + } + }, builder: (context, state) { + final _blocRole = BlocProvider.of(context); + + return Dialog( + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(20))), + width: 900, + child: Column( + children: [ + // Title + const Padding( + padding: EdgeInsets.all(8.0), + child: SizedBox( + child: Text( + "Edit User", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: ColorsManager.secondaryColor), + ), + ), + ), + const Divider(), + Expanded( + child: Row( + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildStep1Indicator(1, "Basics", _blocRole), + _buildStep2Indicator(2, "Spaces", _blocRole), + _buildStep3Indicator( + 3, "Role & Permissions", _blocRole), + ], + ), + ), + ), + Container( + width: 1, + color: ColorsManager.grayBorder, + ), + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + Expanded( + child: _getFormContent(widget.userId), + ), + const SizedBox(height: 20), + ], + ), + ), + ), + ], + ), + ), + const Divider(), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: const Text("Cancel"), + ), + InkWell( + onTap: () { + // _blocRole.add(const CheckEmailEvent()); + + setState(() { + if (currentStep < 3) { + currentStep++; + if (currentStep == 2) { + _blocRole + .add(CheckStepStatus(isEditUser: true)); + } else if (currentStep == 3) { + _blocRole.add(const CheckSpacesStepStatus()); + } + } else { + _blocRole.add(EditInviteUsers( + context: context, + userId: widget.userId!)); + } + }); + }, + child: Text( + currentStep < 3 ? "Next" : "Save", + style: TextStyle( + color: (_blocRole.isCompleteSpaces == false || + _blocRole.isCompleteBasics == false || + _blocRole.isCompleteRolePermissions == + false) && + currentStep == 3 + ? ColorsManager.grayColor + : ColorsManager.secondaryColor), + ), + ), + ], + ), + ), + ], + ), + )); + })); + } + + Widget _getFormContent(userid) { + switch (currentStep) { + case 1: + return BasicsView( + userId: userid, + ); + case 2: + return SpacesAccessView( + userId: userid, + ); + case 3: + return const RolesAndPermission(); + default: + return Container(); + } + } + + int step3 = 0; + + Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + setState(() { + bloc.add(const CheckSpacesStepStatus()); + currentStep = step; + Future.delayed(const Duration(milliseconds: 500), () { + bloc.add(ValidateBasicsStep()); + }); + }); + + if (step3 == 3) { + bloc.add(const CheckRoleStepStatus()); + } + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + SvgPicture.asset( + currentStep == step + ? Assets.currentProcessIcon + : bloc.isCompleteBasics == false + ? Assets.wrongProcessIcon + : bloc.isCompleteBasics == true + ? Assets.completeProcessIcon + : Assets.uncomplete_ProcessIcon, + width: 25, + height: 25, + ), + const SizedBox(width: 10), + Text( + label, + style: TextStyle( + fontSize: 16, + color: currentStep == step + ? ColorsManager.blackColor + : ColorsManager.greyColor, + fontWeight: currentStep == step + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ], + ), + ), + if (step != 3) + Padding( + padding: const EdgeInsets.all(5.0), + child: Padding( + padding: const EdgeInsets.only(left: 12), + child: Container( + height: 60, + width: 1, + color: Colors.grey, + ), + ), + ) + ], + ), + ); + } + + Widget _buildStep2Indicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + setState(() { + currentStep = step; + bloc.add(CheckStepStatus(isEditUser: true)); + if (step3 == 3) { + bloc.add(const CheckRoleStepStatus()); + } + }); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + SvgPicture.asset( + currentStep == step + ? Assets.currentProcessIcon + : bloc.isCompleteSpaces == false + ? Assets.wrongProcessIcon + : bloc.isCompleteSpaces == true + ? Assets.completeProcessIcon + : Assets.uncomplete_ProcessIcon, + width: 25, + height: 25, + ), + const SizedBox(width: 10), + Text( + label, + style: TextStyle( + fontSize: 16, + color: currentStep == step + ? ColorsManager.blackColor + : ColorsManager.greyColor, + fontWeight: currentStep == step + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ], + ), + ), + if (step != 3) + Padding( + padding: const EdgeInsets.all(5.0), + child: Padding( + padding: const EdgeInsets.only(left: 12), + child: Container( + height: 60, + width: 1, + color: Colors.grey, + ), + ), + ) + ], + ), + ); + } + + Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + setState(() { + currentStep = step; + step3 = step; + bloc.add(const CheckSpacesStepStatus()); + bloc.add(CheckStepStatus(isEditUser: true)); + }); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + SvgPicture.asset( + currentStep == step + ? Assets.currentProcessIcon + : bloc.isCompleteRolePermissions == false + ? Assets.wrongProcessIcon + : bloc.isCompleteRolePermissions == true + ? Assets.completeProcessIcon + : Assets.uncomplete_ProcessIcon, + width: 25, + height: 25, + ), + const SizedBox(width: 10), + Text( + label, + style: TextStyle( + fontSize: 16, + color: currentStep == step + ? ColorsManager.blackColor + : ColorsManager.greyColor, + fontWeight: currentStep == step + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ], + ), + ), + if (step != 3) + Padding( + padding: const EdgeInsets.all(5.0), + child: Padding( + padding: const EdgeInsets.only(left: 12), + child: Container( + height: 60, + width: 1, + color: Colors.grey, + ), + ), + ) + ], + ), + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart new file mode 100644 index 00000000..02eac036 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +Future showPopUpFilterMenu({ + required BuildContext context, + Function()? onSortAtoZ, + Function()? onSortZtoA, + Function()? cancelButton, + required Map checkboxStates, + Function()? onOkPressed, + List? list, +}) async { + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; + + await showMenu( + context: context, + position: RelativeRect.fromLTRB( + overlay.size.width / 4, + 240, + overlay.size.width / 4, + 0, + ), + color: ColorsManager.whiteColors, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + items: [ + PopupMenuItem( + onTap: onSortAtoZ, + child: ListTile( + leading: Image.asset( + Assets.AtoZIcon, + width: 25, + ), + title: const Text( + "Sort A to Z", + style: TextStyle(color: Colors.blueGrey), + ), + ), + ), + PopupMenuItem( + onTap: onSortZtoA, + child: ListTile( + leading: Image.asset( + Assets.ZtoAIcon, + width: 25, + ), + title: const Text( + "Sort Z to A", + style: TextStyle(color: Colors.blueGrey), + ), + ), + ), + const PopupMenuDivider(), + const PopupMenuItem( + child: Text( + "Filter by Status", + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + PopupMenuItem( + child: SizedBox( + height: 200, + width: 400, + child: ListView.builder( + itemCount: list?.length ?? 0, + itemBuilder: (context, index) { + final item = list![index]; + return CheckboxListTile( + title: Text(item), + value: checkboxStates[item], + onChanged: (bool? newValue) { + checkboxStates[item] = newValue ?? false; + (context as Element).markNeedsBuild(); + }, + ); + }, + ), + ), + ), + PopupMenuItem( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); // Close the menu + }, + child: const Text("Cancel"), + ), + GestureDetector( + onTap: onOkPressed, + child: const Text( + "OK", + style: TextStyle( + color: ColorsManager.spaceColor, + ), + ), + ), + ], + ), + ), + ], + ); +} diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart index 081fab40..dc2eefc3 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart @@ -4,14 +4,15 @@ import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.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 SpacesAccessView extends StatelessWidget { - const SpacesAccessView({super.key}); + String? userId = ''; + SpacesAccessView({super.key, this.userId}); @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; @@ -109,9 +110,7 @@ class SpacesAccessView extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: Container( color: ColorsManager.whiteColors, - child: TreeView( - bloc: _blocRole, - )))) + child: TreeView(userId: userId)))) ], ), ), @@ -123,155 +122,3 @@ class SpacesAccessView extends StatelessWidget { }); } } - -// ignore: must_be_immutable - -class TreeView extends StatefulWidget { - UsersBloc? bloc; - TreeView({super.key, this.bloc}); - @override - _TreeViewState createState() => _TreeViewState(); -} - -class _TreeViewState extends State { - Widget _buildTree(List nodes, {int level = 0}) { - return Column( - children: nodes.map((node) => _buildNode(node, level: level)).toList(), - ); - } - - Widget _buildNode(TreeNode node, {int level = 0}) { - return Container( - color: node.isHighlighted ? Colors.blue.shade50 : Colors.transparent, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(5.0), - child: Row( - children: [ - GestureDetector( - onTap: () { - setState(() { - node.isChecked = !node.isChecked; - _updateChildrenCheckStatus(node, node.isChecked); - _updateParentCheckStatus(node); - // widget.bloc!.add( - // SelecteId(nodes: widget.bloc!.updatedCommunities)); - }); - }, - child: Image.asset( - _getCheckBoxImage(node), - width: 20, - height: 20, - ), - ), - const SizedBox(width: 15), - Expanded( - child: Padding( - padding: EdgeInsets.only(left: level * 10.0), - child: Row( - children: [ - GestureDetector( - onTap: () { - setState(() { - node.isExpanded = !node.isExpanded; - }); - }, - child: SizedBox( - child: SvgPicture.asset( - node.children.isNotEmpty - ? (node.isExpanded - ? Assets.arrowDown - : Assets.arrowForward) - : Assets.arrowForward, - fit: BoxFit.none, - ), - ), - ), - const SizedBox(width: 20), - Text( - node.title, - style: TextStyle( - fontSize: 16, - color: node.isHighlighted - ? ColorsManager.blackColor - : ColorsManager.textGray, - ), - ), - ], - ), - ), - ), - ], - ), - ), - if (node.isExpanded && node.children.isNotEmpty) - _buildTree(node.children, level: level + 1), - ], - ), - ); - } - - String _getCheckBoxImage(TreeNode node) { - if (node.children.isEmpty) { - return node.isChecked ? Assets.CheckBoxChecked : Assets.emptyBox; - } - if (_areAllChildrenChecked(node)) { - return Assets.CheckBoxChecked; - } else if (_areSomeChildrenChecked(node)) { - return Assets.rectangleCheckBox; - } else { - return Assets.emptyBox; - } - } - - bool _areAllChildrenChecked(TreeNode node) { - return node.children.isNotEmpty && - node.children.every((child) => - child.isChecked && - (child.children.isEmpty || _areAllChildrenChecked(child))); - } - - bool _areSomeChildrenChecked(TreeNode node) { - return node.children.isNotEmpty && - node.children.any((child) => - child.isChecked || - (child.children.isNotEmpty && _areSomeChildrenChecked(child))); - } - - void _updateChildrenCheckStatus(TreeNode node, bool isChecked) { - for (var child in node.children) { - child.isChecked = isChecked; - _updateChildrenCheckStatus(child, isChecked); - } - } - - void _updateParentCheckStatus(TreeNode node) { - TreeNode? parent = _findParent(widget.bloc!.updatedCommunities, node); - if (parent != null) { - setState(() { - parent.isChecked = _areAllChildrenChecked(parent); - _updateParentCheckStatus(parent); - }); - } - } - - TreeNode? _findParent(List nodes, TreeNode target) { - for (var node in nodes) { - if (node.children.contains(target)) { - return node; - } - var parent = _findParent(node.children, target); - if (parent != null) return parent; - } - return null; - } - - @override - Widget build(BuildContext context) { - return SingleChildScrollView( - child: _buildTree(widget.bloc!.updatedCommunities), - ); - } -} diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart index 2a31a91f..82212103 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart @@ -1,8 +1,10 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart'; +import 'package:syncrow_web/services/user_permission.dart'; class UserTableBloc extends Bloc { UserTableBloc() : super(TableInitial()) { @@ -12,79 +14,108 @@ class UserTableBloc extends Bloc { on(_toggleSortUsersByNameDesc); on(_toggleSortUsersByDateOldestToNewest); on(_toggleSortUsersByDateNewestToOldest); + on(_searchUsers); + on(_handlePageChange); + on(_filterUsersByRole); + on(_filterUsersByJobTitle); + on(_filterUsersByCreated); + on(_filterUserActevate); + on(_deleteUser); } - + int itemsPerPage = 10; + int currentPage = 1; List users = []; - List initialUsers = []; // Save the initial state - String currentSortOrder = ''; // Keeps track of the current sorting order - String currentSortOrderDate = ''; // Keeps track of the current sorting order + List initialUsers = []; + String currentSortOrder = ''; + String currentSortOrderDate = ''; + List roleTypes = []; + List jobTitle = []; + List createdBy = []; + List deActivate = []; Future _getUsers(GetUsers event, Emitter emit) async { emit(UsersLoadingState()); try { - users = [ - RolesUserModel( - id: '1', - userName: 'b 1', - userEmail: 'test1@test.com', - action: '', - createdBy: 'Admin', - creationDate: '25/10/2024', - creationTime: '10:30 AM', - status: 'Invited', - ), - RolesUserModel( - id: '2', - userName: 'a 2', - userEmail: 'test2@test.com', - action: '', - createdBy: 'Admin', - creationDate: '24/10/2024', - creationTime: '2:30 PM', - status: 'Active', - ), - RolesUserModel( - id: '3', - userName: 'c 3', - userEmail: 'test3@test.com', - action: '', - createdBy: 'Admin', - creationDate: '23/10/2024', - creationTime: '9:00 AM', - status: 'Disabled', - ), - ]; - // Sort users by newest to oldest as default + roleTypes.clear(); + jobTitle.clear(); + createdBy.clear(); + deActivate.clear(); + users = await UserPermissionApi().fetchUsers(); + for (var user in users) { + roleTypes.add(user.roleType.toString()); + } + for (var user in users) { + jobTitle.add(user.jobTitle.toString()); + } + for (var user in users) { + createdBy.add(user.invitedBy.toString()); + } + for (var user in users) { + deActivate.add(user.status.toString()); + } + roleTypes = roleTypes.toSet().toList(); + jobTitle = jobTitle.toSet().toList(); + createdBy = createdBy.toSet().toList(); + deActivate = deActivate.toSet().toList(); + users.sort((a, b) { - final dateA = _parseDateTime(a.creationDate!); - final dateB = _parseDateTime(b.creationDate!); - return dateB.compareTo(dateA); // Newest to oldest + final dateA = _parseDateTime(a.createdDate); + final dateB = _parseDateTime(b.createdDate); + return dateB.compareTo(dateA); }); - initialUsers = List.from(users); // Save the initial state + initialUsers = List.from(users); + _handlePageChange(ChangePage(1), emit); emit(UsersLoadedState(users: users)); } catch (e) { emit(ErrorState(e.toString())); } } - void _changeUserStatus(ChangeUserStatus event, Emitter emit) { + Future _deleteUser( + DeleteUserEvent event, Emitter emit) async { + emit(UsersLoadingState()); try { - users = users.map((user) { - if (user.id == event.userId) { - return RolesUserModel( - id: user.id, - userName: user.userName, - userEmail: user.userEmail, - createdBy: user.createdBy, - creationDate: user.creationDate, - creationTime: user.creationTime, - status: event.newStatus, - action: user.action, - ); - } - return user; - }).toList(); + bool res = await UserPermissionApi().deleteUserById(event.userId); + if (res == true) { + Navigator.of(event.context).pop(true); + } else { + emit(const ErrorState('Something error')); + } + emit(UsersLoadedState(users: users)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + Future _changeUserStatus( + ChangeUserStatus event, Emitter emit) async { + try { + emit(UsersLoadingState()); + bool res = await UserPermissionApi().changeUserStatusById( + event.userId, event.newStatus == "disabled" ? true : false); + if (res == true) { + add(const GetUsers()); + // users = users.map((user) { + // if (user.uuid == event.userId) { + // return RolesUserModel( + // uuid: user.uuid, + // createdAt: user.createdAt, + // email: user.email, + // firstName: user.firstName, + // lastName: user.lastName, + // roleType: user.roleType, + // status: event.newStatus, + // isEnabled: event.newStatus == "disabled" ? false : true, + // invitedBy: user.invitedBy, + // phoneNumber: user.phoneNumber, + // jobTitle: user.jobTitle, + // createdDate: user.createdDate, + // createdTime: user.createdTime, + // ); + // } + // return user; + // }).toList(); + } emit(UsersLoadedState(users: users)); } catch (e) { emit(ErrorState(e.toString())); @@ -94,16 +125,14 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByNameAsc( SortUsersByNameAsc event, Emitter emit) { if (currentSortOrder == "Asc") { - // If already sorted ascending, reset to the initial state emit(UsersLoadingState()); currentSortOrder = ""; - users = List.from(initialUsers); // Reset to saved initial state + users = List.from(initialUsers); emit(UsersLoadedState(users: users)); } else { - // Sort ascending emit(UsersLoadingState()); currentSortOrder = "Asc"; - users.sort((a, b) => a.userName!.compareTo(b.userName!)); + users.sort((a, b) => a.firstName!.compareTo(b.firstName!)); emit(UsersLoadedState(users: users)); } } @@ -111,7 +140,6 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByNameDesc( SortUsersByNameDesc event, Emitter emit) { if (currentSortOrder == "Desc") { - // If already sorted descending, reset to the initial state emit(UsersLoadingState()); currentSortOrder = ""; users = List.from(initialUsers); // Reset to saved initial state @@ -120,7 +148,7 @@ class UserTableBloc extends Bloc { // Sort descending emit(UsersLoadingState()); currentSortOrder = "Desc"; - users.sort((a, b) => b.userName!.compareTo(a.userName!)); + users.sort((a, b) => b.firstName!.compareTo(a.firstName!)); emit(UsersLoadedState(users: users)); } } @@ -128,19 +156,17 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByDateNewestToOldest( DateNewestToOldestEvent event, Emitter emit) { if (currentSortOrderDate == "NewestToOldest") { - // If already sorted ascending, reset to the initial state emit(UsersLoadingState()); currentSortOrder = ""; currentSortOrderDate = ""; - users = List.from(initialUsers); // Reset to saved initial state + users = List.from(initialUsers); emit(UsersLoadedState(users: users)); } else { - // Sort ascending emit(UsersLoadingState()); users.sort((a, b) { - final dateA = _parseDateTime(a.creationDate!); - final dateB = _parseDateTime(b.creationDate!); - return dateB.compareTo(dateA); // Newest to oldest + final dateA = _parseDateTime(a.createdDate); + final dateB = _parseDateTime(b.createdDate); + return dateB.compareTo(dateA); }); emit(UsersLoadedState(users: users)); } @@ -149,19 +175,17 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByDateOldestToNewest( DateOldestToNewestEvent event, Emitter emit) { if (currentSortOrderDate == "OldestToNewest") { - // If already sorted ascending, reset to the initial state emit(UsersLoadingState()); currentSortOrder = ""; currentSortOrderDate = ""; - users = List.from(initialUsers); // Reset to saved initial state + users = List.from(initialUsers); emit(UsersLoadedState(users: users)); } else { - // Sort ascending emit(UsersLoadingState()); users.sort((a, b) { - final dateA = _parseDateTime(a.creationDate!); - final dateB = _parseDateTime(b.creationDate!); - return dateA.compareTo(dateB); // Newest to oldest + final dateA = _parseDateTime(a.createdDate); + final dateB = _parseDateTime(b.createdDate); + return dateA.compareTo(dateB); }); emit(UsersLoadedState(users: users)); } @@ -169,17 +193,94 @@ class UserTableBloc extends Bloc { DateTime _parseDateTime(String date) { try { - // Split the date into day, month, and year final dateParts = date.split('/'); final day = int.parse(dateParts[0]); final month = int.parse(dateParts[1]); final year = int.parse(dateParts[2]); - - // Split the time into hours and minutes - return DateTime(year, month, day); } catch (e) { throw FormatException('Invalid date or time format: $date '); } } + + Future _searchUsers( + SearchUsers event, Emitter emit) async { + try { + final query = event.query.toLowerCase(); + final filteredUsers = initialUsers.where((user) { + final fullName = "${user.firstName} ${user.lastName}".toLowerCase(); + final email = user.email?.toLowerCase() ?? ""; + return fullName.contains(query) || email.contains(query); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + void _paginateUsers( + int pageNumber, int itemsPerPage, Emitter emit) { + final startIndex = (pageNumber - 1) * itemsPerPage; + final endIndex = startIndex + itemsPerPage; + if (startIndex >= users.length) { + emit(UsersLoadedState(users: const [])); + return; + } + final paginatedUsers = users.sublist( + startIndex, + endIndex > users.length ? users.length : endIndex, + ); + emit(UsersLoadedState(users: paginatedUsers)); + } + + void _handlePageChange(ChangePage event, Emitter emit) { + final itemsPerPage = 10; + final startIndex = (event.pageNumber - 1) * itemsPerPage; + final endIndex = startIndex + itemsPerPage; + if (startIndex >= users.length) { + emit(UsersLoadedState(users: [])); + return; + } + final paginatedUsers = users.sublist( + startIndex, + endIndex > users.length ? users.length : endIndex, + ); + emit(UsersLoadedState(users: paginatedUsers)); + } + + void _filterUsersByRole( + FilterUsersByRoleEvent event, Emitter emit) { + emit(UsersLoadingState()); + final filteredUsers = initialUsers.where((user) { + return event.selectedRoles.contains(user.roleType); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } + + void _filterUsersByJobTitle( + FilterUsersByJobEvent event, Emitter emit) { + emit(UsersLoadingState()); + final filteredUsers = users.where((user) { + return event.selectedJob.contains(user.jobTitle); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } + + void _filterUsersByCreated( + FilterUsersByCreatedEvent event, Emitter emit) { + emit(UsersLoadingState()); + final filteredUsers = initialUsers.where((user) { + return event.selectedCreatedBy.contains(user.invitedBy); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } + + void _filterUserActevate( + FilterUsersByDeActevateEvent event, Emitter emit) { + emit(UsersLoadingState()); + final filteredUsers = initialUsers.where((user) { + return event.selectedActivate.contains(user.status); + }).toList(); + emit(UsersLoadedState(users: filteredUsers)); + } } diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart index a6c77bd3..dbcd9a26 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; sealed class UserTableEvent extends Equatable { const UserTableEvent(); @@ -47,7 +48,6 @@ class StoreUsersEvent extends UserTableEvent { List get props => []; } - class DateNewestToOldestEvent extends UserTableEvent { const DateNewestToOldestEvent(); @@ -60,4 +60,62 @@ class DateOldestToNewestEvent extends UserTableEvent { @override List get props => []; -} \ No newline at end of file +} + +class SearchUsers extends UserTableEvent { + final String query; + SearchUsers(this.query); + @override + List get props => []; +} + +class ChangePage extends UserTableEvent { + final int pageNumber; + + ChangePage(this.pageNumber); + + @override + List get props => [pageNumber]; +} + +class DeleteUserEvent extends UserTableEvent { + final String userId; + final BuildContext context; + + const DeleteUserEvent(this.userId, this.context); + + @override + List get props => [userId, context]; +} + +class FilterUsersByRoleEvent extends UserTableEvent { + final List selectedRoles; + + FilterUsersByRoleEvent(this.selectedRoles); + @override + List get props => [selectedRoles]; +} + +class FilterUsersByJobEvent extends UserTableEvent { + final List selectedJob; + + FilterUsersByJobEvent(this.selectedJob); + @override + List get props => [selectedJob]; +} + +class FilterUsersByCreatedEvent extends UserTableEvent { + final List selectedCreatedBy; + + FilterUsersByCreatedEvent(this.selectedCreatedBy); + @override + List get props => [selectedCreatedBy]; +} + +class FilterUsersByDeActevateEvent extends UserTableEvent { + final List selectedActivate; + + FilterUsersByDeActevateEvent(this.selectedActivate); + @override + List get props => [selectedActivate]; +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart index f42c0c03..4f777ee3 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart @@ -81,15 +81,17 @@ class _DynamicTableScreenState extends State scrollDirection: Axis.horizontal, child: Container( decoration: containerDecoration.copyWith( + color: ColorsManager.whiteColors, borderRadius: const BorderRadius.all(Radius.circular(20))), child: FittedBox( child: Column( children: [ // Header Row with Resizable Columns Container( + width: MediaQuery.of(context).size.width, decoration: containerDecoration.copyWith( color: ColorsManager.circleRolesBackground, - borderRadius: BorderRadius.only( + borderRadius: const BorderRadius.only( topLeft: Radius.circular(15), topRight: Radius.circular(15))), child: Row( @@ -167,56 +169,96 @@ class _DynamicTableScreenState extends State ), ), // Data Rows with Dividers - Container( - decoration: containerDecoration.copyWith( - color: ColorsManager.whiteColors, - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(15), - bottomRight: Radius.circular(15))), - child: Column( - children: widget.rows.map((row) { - int rowIndex = widget.rows.indexOf(row); - return Column( - children: [ - Container( - child: Padding( - padding: const EdgeInsets.only( - left: 5, top: 10, right: 5, bottom: 10), - child: Row( - children: List.generate(row.length, (index) { - return SizedBox( - width: columnWidths[index], - child: SizedBox( - child: Padding( - padding: const EdgeInsets.only( - left: 15, right: 10), - child: row[index], - ), + widget.rows.isEmpty + ? Container( + child: SizedBox( + height: MediaQuery.of(context).size.height / 2, + child: Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + SvgPicture.asset(Assets.emptyTable), + const SizedBox( + height: 15, ), - ); - }), - ), + const Text( + 'No Users', + style: TextStyle( + color: ColorsManager.lightGrayColor, + fontSize: 16, + fontWeight: FontWeight.w700), + ) + ], + ), + ], ), ), - if (rowIndex < widget.rows.length - 1) - Row( - children: List.generate(widget.titles.length, - (index) { - return SizedBox( - width: columnWidths[index], - child: const Divider( - color: ColorsManager.boxDivider, - thickness: 1, - height: 1, - ), + ), + ) + : Center( + child: Container( + // height: MediaQuery.of(context).size.height * 0.59, + width: MediaQuery.of(context).size.width, + decoration: containerDecoration.copyWith( + color: ColorsManager.whiteColors, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(15), + bottomRight: Radius.circular(15))), + child: ListView.builder( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: widget.rows.length, + itemBuilder: (context, rowIndex) { + final row = widget.rows[rowIndex]; + return Column( + children: [ + Container( + child: Padding( + padding: const EdgeInsets.only( + left: 5, + top: 10, + right: 5, + bottom: 10), + child: Row( + children: + List.generate(row.length, (index) { + return SizedBox( + width: columnWidths[index], + child: SizedBox( + child: Padding( + padding: const EdgeInsets.only( + left: 15, right: 10), + child: row[index], + ), + ), + ); + }), + ), + ), + ), + if (rowIndex < widget.rows.length - 1) + Row( + children: List.generate( + widget.titles.length, (index) { + return SizedBox( + width: columnWidths[index], + child: const Divider( + color: ColorsManager.boxDivider, + thickness: 1, + height: 1, + ), + ); + }), + ), + ], ); - })) - // Add a Divider below each row except the last one - ], - ); - }).toList(), - ), - ), + }, + ), + ), + ), ], ), ), diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart index 758f062b..8d838ba2 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart @@ -1,7 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:number_pagination/number_pagination.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart'; @@ -15,7 +19,8 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class UsersPage extends StatelessWidget { - const UsersPage({super.key}); + UsersPage({super.key}); + @override Widget build(BuildContext context) { final TextEditingController searchController = TextEditingController(); @@ -44,9 +49,9 @@ class UsersPage extends StatelessWidget { padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(20)), - color: status == "Invited" + color: status == "invited" ? ColorsManager.invitedOrange.withOpacity(0.5) - : status == "Active" + : status == "active" ? ColorsManager.activeGreen.withOpacity(0.5) : ColorsManager.disabledPink.withOpacity(0.5), ), @@ -60,9 +65,9 @@ class UsersPage extends StatelessWidget { Text( status, style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: status == "Invited" + color: status == "invited" ? ColorsManager.invitedOrangeText - : status == "Active" + : status == "active" ? ColorsManager.activeGreenText : ColorsManager.disabledRedText, fontWeight: FontWeight.w400, @@ -81,23 +86,14 @@ class UsersPage extends StatelessWidget { required Function()? onTap}) { return Center( child: InkWell( - onTap: () { - final newStatus = status == 'Active' - ? 'Disabled' - : status == 'Disabled' - ? 'Invited' - : 'Active'; - context - .read() - .add(ChangeUserStatus(userId: userId, newStatus: newStatus)); - }, + onTap: onTap, child: Padding( padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), child: SvgPicture.asset( - status == "Invited" + status == "invited" ? Assets.invitedIcon - : status == "Active" + : status == "active" ? Assets.activeUser : Assets.deActiveUser, height: 35, @@ -113,12 +109,14 @@ class UsersPage extends StatelessWidget { final _blocRole = BlocProvider.of(context); if (state is UsersLoadingState) { + _blocRole.add(ChangePage(_blocRole.currentPage)); + return const Center(child: CircularProgressIndicator()); } else if (state is UsersLoadedState) { return Padding( padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: ListView( + shrinkWrap: true, children: [ Row( children: [ @@ -131,6 +129,9 @@ class UsersPage extends StatelessWidget { width: screenSize.width * 0.4, child: TextFormField( controller: searchController, + onChanged: (value) { + context.read().add(SearchUsers(value)); + }, style: const TextStyle(color: Colors.black), decoration: textBoxDecoration(radios: 15)!.copyWith( fillColor: ColorsManager.whiteColors, @@ -158,9 +159,9 @@ class UsersPage extends StatelessWidget { builder: (BuildContext context) { return const AddNewUserDialog(); }, - ).then((listDevice) { - if (listDevice != null) { - + ).then((v) { + if (v != null) { + _blocRole.add(const GetUsers()); } }); }, @@ -201,6 +202,66 @@ class UsersPage extends StatelessWidget { }, ); } + if (columnIndex == 2) { + final Map checkboxStates = { + for (var item in _blocRole.jobTitle) + item: false, // Initialize with false + }; + + showPopUpFilterMenu( + list: _blocRole.jobTitle, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole.add(FilterUsersByJobEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + if (columnIndex == 3) { + final Map checkboxStates = { + for (var item in _blocRole.roleTypes) + item: false, // Initialize with false + }; + + showPopUpFilterMenu( + list: _blocRole.roleTypes, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole.add(FilterUsersByRoleEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } if (columnIndex == 4) { showDateFilterMenu( context: context, @@ -217,6 +278,37 @@ class UsersPage extends StatelessWidget { }, ); } + if (columnIndex == 6) { + final Map checkboxStates = { + for (var item in _blocRole.createdBy) + item: false, // Initialize with false + }; + + showPopUpFilterMenu( + list: _blocRole.createdBy, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole + .add(FilterUsersByCreatedEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } if (columnIndex == 8) { showDeActivateFilterMenu( context: context, @@ -248,19 +340,37 @@ class UsersPage extends StatelessWidget { ], rows: state.users.map((user) { return [ - Text(user.userName!), - Text(user.userEmail!), - const Text("Test"), - const Text("Member"), - Text(user.creationDate!), - Text(user.creationTime!), - Text(user.createdBy!), + Text('${user.firstName} ${user.lastName}'), + Text(user.email ?? ''), + Text(user.jobTitle ?? ''), + Text(user.roleType ?? ''), + Text(user.createdDate ?? ''), + Text(user.createdTime ?? ''), + Text(user.invitedBy), changeIconStatus( - status: user.status!, - userId: user.id!, - onTap: () {}, + status: + user.isEnabled == false ? 'disabled' : user.status, + userId: user.uuid, + onTap: user.status != "invited" + ? () { + final newStatus = user.status == 'active' + ? 'disabled' + : user.status == 'disabled' + ? 'invited' + : 'active'; + context.read().add( + ChangeUserStatus( + userId: user.uuid, + newStatus: user.isEnabled == false + ? 'disabled' + : user.status)); + } + : null, + ), + status( + status: + user.isEnabled == false ? 'disabled' : user.status, ), - status(status: user.status!), Row( children: [ // actionButton( @@ -269,17 +379,80 @@ class UsersPage extends StatelessWidget { // ), actionButton( title: "Edit", - onTap: () {}, + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return EditUserDialog(userId: user.uuid); + }, + ).then((v) { + if (v != null) { + if (v != null) { + _blocRole.add(const GetUsers()); + } + } + }); + }, ), actionButton( title: "Delete", - onTap: () {}, + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return DeleteUserDialog( + onTapDelete: () { + _blocRole.add( + DeleteUserEvent(user.uuid, context)); + }, + ); + }, + ).then((v) { + if (v != null) { + if (v != null) { + _blocRole.add(const GetUsers()); + } + } + }); + }, ), ], ), ]; }).toList(), ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + width: 500, + child: NumberPagination( + buttonRadius: 10, + selectedButtonColor: ColorsManager.secondaryColor, + buttonUnSelectedBorderColor: ColorsManager.grayBorder, + lastPageIcon: + const Icon(Icons.keyboard_double_arrow_right), + firstPageIcon: + const Icon(Icons.keyboard_double_arrow_left), + totalPages: + (_blocRole.users.length / _blocRole.itemsPerPage) + .ceil(), + currentPage: _blocRole.currentPage, + onPageChanged: (int pageNumber) { + _blocRole.currentPage = pageNumber; + context + .read() + .add(ChangePage(pageNumber)); + }, + ), + ), + ], + ), + ), ], ), ); diff --git a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart index da003536..44944ed3 100644 --- a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart +++ b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart @@ -77,7 +77,7 @@ class RolesAndPermissionPage extends StatelessWidget { ), scaffoldBody: BlocProvider( create: (context) => UserTableBloc()..add(const GetUsers()), - child: const UsersPage(), + child: UsersPage(), ) // _blocRole.tapSelect == false // ? UsersPage( diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 5d2464e6..abf151fb 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -252,6 +252,7 @@ class CommunitySpaceManagementApi { path: ApiEndpoints.getSpaceHierarchy .replaceAll('{communityId}', communityId), expectedResponseModel: (json) { + print(json); final spaceModels = (json['data'] as List) .map((spaceJson) => SpaceModel.fromJson(spaceJson)) .toList(); diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index 0e31795a..f62437ac 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -1,6 +1,9 @@ import 'dart:convert'; import 'package:dio/dio.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/edit_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; @@ -8,6 +11,25 @@ import 'package:syncrow_web/utils/constants/api_const.dart'; class UserPermissionApi { static final HTTPService _httpService = HTTPService(); + Future> fetchUsers() async { + try { + final response = await _httpService.get( + path: ApiEndpoints.getUsers, + showServerMessage: true, + expectedResponseModel: (json) { + debugPrint('fetchUsers Response: $json'); + final List data = + json['data'] ?? []; // Default to an empty list if no data + return data.map((item) => RolesUserModel.fromJson(item)).toList(); + }, + ); + return response; + } catch (e, stackTrace) { + debugPrint('Error in fetchUsers: $e'); + rethrow; + } + } + fetchRoles() async { final response = await _httpService.get( path: ApiEndpoints.roleTypes, @@ -67,6 +89,7 @@ class UserPermissionApi { } }, ); + print('sendInviteUser=$body'); return response ?? []; } on DioException catch (e) { @@ -107,4 +130,95 @@ class UserPermissionApi { return e.toString(); } } + + Future fetchUserById(userUuid) async { + final response = await _httpService.get( + path: ApiEndpoints.getUserById.replaceAll("{userUuid}", userUuid), + showServerMessage: true, + expectedResponseModel: (json) { + EditUserModel res = EditUserModel.fromJson(json['data']); + return res; + }, + ); + return response; + } + + Future editInviteUser({ + String? firstName, + String? userId, + String? lastName, + String? jobTitle, + String? phoneNumber, + String? roleUuid, + List? spaceUuids, + }) async { + try { + final body = { + "firstName": firstName, + "lastName": lastName, + "jobTitle": jobTitle != '' ? jobTitle : " ", + "phoneNumber": phoneNumber != '' ? phoneNumber : " ", + "roleUuid": roleUuid, + "projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c", + "spaceUuids": spaceUuids, + }; + final response = await _httpService.put( + path: ApiEndpoints.editUser.replaceAll('{inviteUserUuid}', userId!), + body: jsonEncode(body), + expectedResponseModel: (json) { + if (json['statusCode'] != 400) { + return json["success"]; + } else { + return false; + } + }, + ); + return response; + } on DioException catch (e) { + return false; + } catch (e) { + return false; + } + } + + Future deleteUserById(userUuid) async { + try { + final response = await _httpService.delete( + path: ApiEndpoints.deleteUser.replaceAll("{inviteUserUuid}", userUuid), + showServerMessage: true, + expectedResponseModel: (json) { + return json['success']; + }, + ); + return response; + } catch (e) { + return false; + } + } + + Future changeUserStatusById(userUuid, status) async { + try { + Map bodya = { + "disable": status, + "projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c" + }; + print('changeUserStatusById==$bodya'); + print('changeUserStatusById==$userUuid'); + + final response = await _httpService.put( + path: ApiEndpoints.changeUserStatus + .replaceAll("{invitedUserUuid}", userUuid), + body: bodya, + expectedResponseModel: (json) { + print('changeUserStatusById==${json['success']}'); + return json['success']; + }, + ); + + return response; + } catch (e) { + return false; + print(e); + } + } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index da746fbd..a229c5f6 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -100,7 +100,13 @@ abstract class ApiEndpoints { static const String roleTypes = '/role/types'; static const String permission = '/permission/{roleUuid}'; static const String inviteUser = '/invite-user'; + static const String checkEmail = '/invite-user/check-email'; + static const String getUsers = '/projects/${projectUuid}/user'; + static const String getUserById = '/projects/${projectUuid}/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 updateAutomation = '/automation/{automationId}'; - // https://syncrow-dev.azurewebsites.net/invite-user/check-email } diff --git a/macos/Podfile.lock b/macos/Podfile.lock new file mode 100644 index 00000000..0639648b --- /dev/null +++ b/macos/Podfile.lock @@ -0,0 +1,36 @@ +PODS: + - flutter_secure_storage_macos (6.1.1): + - FlutterMacOS + - FlutterMacOS (1.0.0) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) + - FlutterMacOS (from `Flutter/ephemeral`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) + +EXTERNAL SOURCES: + flutter_secure_storage_macos: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos + FlutterMacOS: + :path: Flutter/ephemeral + path_provider_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin + +SPEC CHECKSUMS: + flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9 + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + +PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 + +COCOAPODS: 1.16.2 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index e1af4c91..eec16af6 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -21,6 +21,8 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 108157F896CD9F637B06D7C0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DAF1C60594A51D692304366 /* Pods_Runner.framework */; }; + 2D0F1F294F673EF0DB5E4CA1 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */; }; 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; @@ -60,11 +62,12 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 24D7BEF98D33245EFB9F6A1B /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* syncrow_web.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "syncrow_web.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* syncrow_web.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = syncrow_web.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -76,8 +79,15 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 5DAF1C60594A51D692304366 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 81F2F315AC5109F6F5D27BE6 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 96C46007EE0A4E9E1D6D74CE /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + A604E311B663FBF4B7C54DC5 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + AB949539E0D0A8E2BDAB9ADF /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F244F079A053D959E1C5C362 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -85,6 +95,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 2D0F1F294F673EF0DB5E4CA1 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -92,6 +103,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 108157F896CD9F637B06D7C0 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -125,6 +137,7 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + 75DCDFECC7757C5159E8F0C5 /* Pods */, ); sourceTree = ""; }; @@ -172,9 +185,25 @@ path = Runner; sourceTree = ""; }; + 75DCDFECC7757C5159E8F0C5 /* Pods */ = { + isa = PBXGroup; + children = ( + 24D7BEF98D33245EFB9F6A1B /* Pods-Runner.debug.xcconfig */, + F244F079A053D959E1C5C362 /* Pods-Runner.release.xcconfig */, + AB949539E0D0A8E2BDAB9ADF /* Pods-Runner.profile.xcconfig */, + 96C46007EE0A4E9E1D6D74CE /* Pods-RunnerTests.debug.xcconfig */, + A604E311B663FBF4B7C54DC5 /* Pods-RunnerTests.release.xcconfig */, + 81F2F315AC5109F6F5D27BE6 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + 5DAF1C60594A51D692304366 /* Pods_Runner.framework */, + E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -186,6 +215,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + A1935203066F42991FF0ED43 /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -204,11 +234,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 8ECFD939A4D371A145DBA191 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + 92D754792F50A5D35F6D5AEE /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -329,6 +361,67 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 8ECFD939A4D371A145DBA191 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 92D754792F50A5D35F6D5AEE /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + A1935203066F42991FF0ED43 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -380,6 +473,7 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 96C46007EE0A4E9E1D6D74CE /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -394,6 +488,7 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A604E311B663FBF4B7C54DC5 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -408,6 +503,7 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 81F2F315AC5109F6F5D27BE6 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a16..21a3cc14 100644 --- a/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/pubspec.lock b/pubspec.lock index 86d132c3..d1c39c65 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -392,6 +392,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + number_pagination: + dependency: "direct main" + description: + name: number_pagination + sha256: "75d3a28616196e7c8df431d0fb7c48e811e462155f4cf3b5b4167b3408421327" + url: "https://pub.dev" + source: hosted + version: "1.1.6" path: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6951987c..786a39c9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,6 +53,7 @@ dependencies: uuid: ^4.4.2 time_picker_spinner: ^1.0.0 intl_phone_field: ^3.2.0 + number_pagination: ^1.1.6 dev_dependencies: flutter_test: From e44c3ae79647429db750084bf2d0d07969d961b1 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 19:27:42 +0400 Subject: [PATCH 049/175] pass created subpaces --- .../dialog/create_space_model_dialog.dart | 1 + .../views/add_device_type_model_widget.dart | 19 +++++++++---------- .../widgets/device_type_tile_widget.dart | 2 ++ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index a84b6409..c70e3c56 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -78,6 +78,7 @@ class CreateSpaceModelDialog extends StatelessWidget { context: context, builder: (context) => AddDeviceTypeModelWidget( products: products, + subspaces: subspaces, ), ); if (result == true) { diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index ad71116a..ab0a0cb0 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -12,13 +12,14 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final List? products; final ValueChanged>? onProductsSelected; final List? initialSelectedProducts; + final List? subspaces; - const AddDeviceTypeModelWidget({ - super.key, - this.products, - this.initialSelectedProducts, - this.onProductsSelected, - }); + const AddDeviceTypeModelWidget( + {super.key, + this.products, + this.initialSelectedProducts, + this.onProductsSelected, + this.subspaces}); @override Widget build(BuildContext context) { @@ -46,9 +47,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20.0), child: ScrollableGridViewWidget( - products: products, - crossAxisCount: crossAxisCount, - ), + products: products, crossAxisCount: crossAxisCount), ), ), ], diff --git a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart index 1190bc5c..1f39fc22 100644 --- a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart +++ b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart @@ -3,7 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_icon_widget.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_name_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; From 5bd257ee5696b00297788bf88cda3b6e33142b04 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 19:56:39 +0400 Subject: [PATCH 050/175] added tag model assignment ui --- .../views/assign_tag_models_dialog.dart | 126 ++++++++++++++++++ .../space_model/models/tag_model.dart | 18 +-- .../views/add_device_type_model_widget.dart | 98 +++++++------- 3 files changed, 181 insertions(+), 61 deletions(-) create mode 100644 lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart diff --git a/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart new file mode 100644 index 00000000..1c7ab2d0 --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart @@ -0,0 +1,126 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class AssignTagModelsDialog extends StatefulWidget { + final List? products; + final List? subspaces; + final List? initialTags; + final ValueChanged>? onTagsAssigned; + + const AssignTagModelsDialog({ + Key? key, + required this.products, + required this.subspaces, + this.initialTags, + this.onTagsAssigned, + }) : super(key: key); + + @override + AssignTagModelsDialogState createState() => AssignTagModelsDialogState(); +} + +class AssignTagModelsDialogState extends State { + late List tags; + + @override + void initState() { + super.initState(); + + if (widget.products != null) { + print(widget.products); + tags = []; + } + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Assign Tags'), + backgroundColor: ColorsManager.whiteColors, + content: SingleChildScrollView( + child: Container( + width: MediaQuery.of(context).size.width * 0.9, + child: DataTable( + columns: const [ + DataColumn(label: Text('#')), + DataColumn(label: Text('Device')), + DataColumn(label: Text('Tag')), + DataColumn(label: Text('Location')), + ], + rows: tags.asMap().entries.map((entry) { + final index = entry.key + 1; + final tagModel = entry.value; + return DataRow(cells: [ + DataCell(Text(index.toString())), + DataCell(Text(tagModel.product?.name ?? 'Unknown')), + DataCell( + DropdownButton( + value: tagModel.tag, + onChanged: (value) { + setState(() { + tagModel.tag = value!; + }); + }, + items: List.generate(10, (index) { + final tag = 'Tag ${index + 1}'; + return DropdownMenuItem(value: tag, child: Text(tag)); + }), + ), + ), + DataCell( + DropdownButton( + value: widget.subspaces + ?.firstWhere( + (subspace) => + subspace.subspaceName == 'ssdsdf', + ) + .subspaceName ?? + 'None', + onChanged: (value) {}, + items: widget.subspaces! + .map((subspace) => DropdownMenuItem( + value: subspace.subspaceName, + child: Text(subspace.subspaceName), + )) + .toList(), + ), + ), + ]); + }).toList(), + ), + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Back'), + style: ElevatedButton.styleFrom( + backgroundColor: ColorsManager.boxColor, + foregroundColor: ColorsManager.blackColor, + ), + ), + ElevatedButton( + onPressed: () { + Navigator.of(context).pop(); + if (widget.onTagsAssigned != null) { + widget.onTagsAssigned!(tags); + } + }, + child: const Text('Save'), + style: ElevatedButton.styleFrom( + backgroundColor: ColorsManager.secondaryColor, + foregroundColor: ColorsManager.whiteColors, + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart index 356368fc..a9512107 100644 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -1,29 +1,20 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; class TagModel { - final String uuid; - final DateTime createdAt; - final DateTime updatedAt; - final String tag; - final bool disabled; + String? uuid; + String tag; final ProductModel? product; TagModel({ - required this.uuid, - required this.createdAt, - required this.updatedAt, + this.uuid, required this.tag, - required this.disabled, this.product, }); factory TagModel.fromJson(Map json) { return TagModel( uuid: json['uuid'] ?? '', - createdAt: DateTime.parse(json['createdAt']), - updatedAt: DateTime.parse(json['updatedAt']), tag: json['tag'] ?? '', - disabled: json['disabled'] ?? false, product: json['product'] != null ? ProductModel.fromMap(json['product']) : null, @@ -33,10 +24,7 @@ class TagModel { Map toJson() { return { 'uuid': uuid, - 'createdAt': createdAt.toIso8601String(), - 'updatedAt': updatedAt.toIso8601String(), 'tag': tag, - 'disabled': disabled, 'product': product?.toMap(), }; } diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index ab0a0cb0..32828898 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; @@ -31,56 +32,61 @@ class AddDeviceTypeModelWidget extends StatelessWidget { : 3; return BlocProvider( - create: (_) => AddDeviceTypeModelBloc(initialSelectedProducts ?? []), - child: AlertDialog( - title: const Text('Add Devices'), - backgroundColor: ColorsManager.whiteColors, - content: SingleChildScrollView( - child: Container( - width: size.width * 0.9, - height: size.height * 0.65, - color: ColorsManager.textFieldGreyColor, - child: Column( - children: [ - const SizedBox(height: 16), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: ScrollableGridViewWidget( - products: products, crossAxisCount: crossAxisCount), - ), + create: (_) => AddDeviceTypeModelBloc(initialSelectedProducts ?? []), + child: Builder( + builder: (context) => AlertDialog( + title: const Text('Add Devices'), + backgroundColor: ColorsManager.whiteColors, + content: SingleChildScrollView( + child: Container( + width: size.width * 0.9, + height: size.height * 0.65, + color: ColorsManager.textFieldGreyColor, + child: Column( + children: [ + const SizedBox(height: 16), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: ScrollableGridViewWidget( + products: products, crossAxisCount: crossAxisCount), + ), + ), + ], ), - ], - ), - ), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ActionButton( - label: 'Cancel', - backgroundColor: ColorsManager.boxColor, - foregroundColor: ColorsManager.blackColor, - onPressed: () => Navigator.of(context).pop(), ), - ActionButton( - label: 'Continue', - backgroundColor: ColorsManager.secondaryColor, - foregroundColor: ColorsManager.whiteColors, - onPressed: () { - Navigator.of(context).pop(); - if (onProductsSelected != null) { - final selectedProducts = - context.read().state; - onProductsSelected!(selectedProducts); - } - }, + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ActionButton( + label: 'Cancel', + backgroundColor: ColorsManager.boxColor, + foregroundColor: ColorsManager.blackColor, + onPressed: () => Navigator.of(context).pop(), + ), + ActionButton( + label: 'Continue', + backgroundColor: ColorsManager.secondaryColor, + foregroundColor: ColorsManager.whiteColors, + onPressed: () async { + print(products); + print("dfsdf"); + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AssignTagModelsDialog( + products: products, + subspaces: subspaces, + ), + ); + }, + ), + ], ), ], ), - ], - ), - ); + )); } } From 0fda5457ae9600423c2c4f6ee786f9479ab302eb Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 21:16:59 +0400 Subject: [PATCH 051/175] fixed tag model --- .../views/assign_tag_models_dialog.dart | 34 ++++++++++--------- .../model/selected_product_model.dart | 4 ++- .../all_spaces/model/space_model.dart | 1 + .../widgets/add_device_type_widget.dart | 4 +-- .../space_model/models/tag_model.dart | 9 +++-- .../tag_model/bloc/add_device_model_bloc.dart | 6 ++-- .../bloc/add_device_type_model_event.dart | 3 +- .../views/add_device_type_model_widget.dart | 28 ++++++++------- .../widgets/device_type_tile_widget.dart | 5 ++- 9 files changed, 53 insertions(+), 41 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart index 1c7ab2d0..acdb75d8 100644 --- a/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -9,11 +10,13 @@ class AssignTagModelsDialog extends StatefulWidget { final List? subspaces; final List? initialTags; final ValueChanged>? onTagsAssigned; + final List addedProducts; const AssignTagModelsDialog({ Key? key, required this.products, required this.subspaces, + required this.addedProducts, this.initialTags, this.onTagsAssigned, }) : super(key: key); @@ -24,15 +27,17 @@ class AssignTagModelsDialog extends StatefulWidget { class AssignTagModelsDialogState extends State { late List tags; + late List selectedProducts; @override void initState() { super.initState(); - + if (widget.products != null) { print(widget.products); tags = []; } + selectedProducts = widget.addedProducts; } @override @@ -50,19 +55,18 @@ class AssignTagModelsDialogState extends State { DataColumn(label: Text('Tag')), DataColumn(label: Text('Location')), ], - rows: tags.asMap().entries.map((entry) { + rows: selectedProducts.asMap().entries.map((entry) { final index = entry.key + 1; - final tagModel = entry.value; + final selectedProduct = entry.value; return DataRow(cells: [ DataCell(Text(index.toString())), - DataCell(Text(tagModel.product?.name ?? 'Unknown')), + DataCell(Text(selectedProduct.productName ?? 'Unknown')), DataCell( DropdownButton( - value: tagModel.tag, + value: + 'Tag 1', // Static text that matches an item in the list onChanged: (value) { - setState(() { - tagModel.tag = value!; - }); + // Handle value change if needed }, items: List.generate(10, (index) { final tag = 'Tag ${index + 1}'; @@ -72,14 +76,12 @@ class AssignTagModelsDialogState extends State { ), DataCell( DropdownButton( - value: widget.subspaces - ?.firstWhere( - (subspace) => - subspace.subspaceName == 'ssdsdf', - ) - .subspaceName ?? - 'None', - onChanged: (value) {}, + value: widget.subspaces?.isNotEmpty == true + ? widget.subspaces!.first.subspaceName + : null, + onChanged: (value) { + // Handle value changes here + }, items: widget.subspaces! .map((subspace) => DropdownMenuItem( value: subspace.subspaceName, diff --git a/lib/pages/spaces_management/all_spaces/model/selected_product_model.dart b/lib/pages/spaces_management/all_spaces/model/selected_product_model.dart index 9a06698f..31d7f6f8 100644 --- a/lib/pages/spaces_management/all_spaces/model/selected_product_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/selected_product_model.dart @@ -1,13 +1,15 @@ class SelectedProduct { final String productId; int count; + final String productName; - SelectedProduct({required this.productId, required this.count}); + SelectedProduct({required this.productId, required this.count, required this.productName}); Map toJson() { return { 'productId': productId, 'count': count, + 'productName': productName, }; } diff --git a/lib/pages/spaces_management/all_spaces/model/space_model.dart b/lib/pages/spaces_management/all_spaces/model/space_model.dart index df6550f8..7406d919 100644 --- a/lib/pages/spaces_management/all_spaces/model/space_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/space_model.dart @@ -90,6 +90,7 @@ class SpaceModel { return SelectedProduct( productId: product['product']['uuid'], count: product['productCount'], + productName: '', ); }).toList() : [], diff --git a/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart index 40759b58..ed764250 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart @@ -114,7 +114,7 @@ class _AddDeviceWidgetState extends State { Widget _buildDeviceTypeTile(ProductModel product, Size size) { final selectedProduct = productCounts.firstWhere( (p) => p.productId == product.uuid, - orElse: () => SelectedProduct(productId: product.uuid, count: 0), + orElse: () => SelectedProduct(productId: product.uuid, count: 0, productName: product.catName), ); return SizedBox( @@ -143,7 +143,7 @@ class _AddDeviceWidgetState extends State { if (newCount > 0) { if (!productCounts.contains(selectedProduct)) { productCounts - .add(SelectedProduct(productId: product.uuid, count: newCount)); + .add(SelectedProduct(productId: product.uuid, count: newCount, productName: product.catName)); } else { selectedProduct.count = newCount; } diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart index a9512107..820b9515 100644 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -1,19 +1,25 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:uuid/uuid.dart'; class TagModel { String? uuid; String tag; final ProductModel? product; + String internalId; TagModel({ this.uuid, required this.tag, this.product, - }); + String? internalId, + }) : internalId = internalId ?? const Uuid().v4(); factory TagModel.fromJson(Map json) { + final String internalId = json['internalId'] ?? const Uuid().v4(); + return TagModel( uuid: json['uuid'] ?? '', + internalId: internalId, tag: json['tag'] ?? '', product: json['product'] != null ? ProductModel.fromMap(json['product']) @@ -29,4 +35,3 @@ class TagModel { }; } } - diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart index 1352e97c..1049fdce 100644 --- a/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart @@ -13,19 +13,19 @@ class AddDeviceTypeModelBloc UpdateProductCountEvent event, Emitter> emit) { final existingProduct = state.firstWhere( (p) => p.productId == event.productId, - orElse: () => SelectedProduct(productId: event.productId, count: 0), + orElse: () => SelectedProduct(productId: event.productId, count: 0,productName: event.productName ), ); if (event.count > 0) { if (!state.contains(existingProduct)) { emit([ ...state, - SelectedProduct(productId: event.productId, count: event.count) + SelectedProduct(productId: event.productId, count: event.count, productName: event.productName) ]); } else { final updatedList = state.map((p) { if (p.productId == event.productId) { - return SelectedProduct(productId: p.productId, count: event.count); + return SelectedProduct(productId: p.productId, count: event.count, productName: p.productName); } return p; }).toList(); diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart index a3feaad8..c9594020 100644 --- a/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart @@ -8,8 +8,9 @@ abstract class AddDeviceTypeModelEvent extends Equatable { class UpdateProductCountEvent extends AddDeviceTypeModelEvent { final String productId; final int count; + final String productName; - UpdateProductCountEvent({required this.productId, required this.count}); + UpdateProductCountEvent({required this.productId, required this.count, required this.productName}); @override List get props => [productId, count]; diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index 32828898..7c9204ef 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -1,5 +1,6 @@ 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/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; @@ -60,10 +61,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - ActionButton( + CancelButton( label: 'Cancel', - backgroundColor: ColorsManager.boxColor, - foregroundColor: ColorsManager.blackColor, onPressed: () => Navigator.of(context).pop(), ), ActionButton( @@ -71,16 +70,19 @@ class AddDeviceTypeModelWidget extends StatelessWidget { backgroundColor: ColorsManager.secondaryColor, foregroundColor: ColorsManager.whiteColors, onPressed: () async { - print(products); - print("dfsdf"); - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => AssignTagModelsDialog( - products: products, - subspaces: subspaces, - ), - ); + final currentState = + context.read().state; + if (currentState.isNotEmpty) { + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AssignTagModelsDialog( + products: products, + subspaces: subspaces, + addedProducts: currentState, + ), + ); + } }, ), ], diff --git a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart index 1f39fc22..38159057 100644 --- a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart +++ b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart @@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_icon_widget.dart'; @@ -25,7 +24,7 @@ class DeviceTypeTileWidget extends StatelessWidget { Widget build(BuildContext context) { final selectedProduct = productCounts.firstWhere( (p) => p.productId == product.uuid, - orElse: () => SelectedProduct(productId: product.uuid, count: 0), + orElse: () => SelectedProduct(productId: product.uuid, count: 0, productName: product.catName), ); return Card( @@ -49,7 +48,7 @@ class DeviceTypeTileWidget extends StatelessWidget { onCountChanged: (newCount) { context.read().add( UpdateProductCountEvent( - productId: product.uuid, count: newCount), + productId: product.uuid, count: newCount,productName: product.catName), ); }, ), From a31eb27c929a175008cd4471e8f934f7f1147637 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 23:22:30 +0400 Subject: [PATCH 052/175] addign tag model --- .../views/assign_tag_models_dialog.dart | 186 +++++++++++++----- .../bloc/space_management_bloc.dart | 37 ++-- .../model/selected_product_model.dart | 5 +- .../all_spaces/model/space_model.dart | 11 -- .../widgets/add_device_type_widget.dart | 4 +- .../widgets/community_structure_widget.dart | 4 +- .../models/subspace_template_model.dart | 2 +- .../space_model/models/tag_model.dart | 17 +- .../tag_model/bloc/add_device_model_bloc.dart | 6 +- .../bloc/add_device_type_model_event.dart | 4 +- .../widgets/device_type_tile_widget.dart | 11 +- lib/services/space_mana_api.dart | 4 - lib/utils/color_manager.dart | 1 + 13 files changed, 190 insertions(+), 102 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart index acdb75d8..885f483b 100644 --- a/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart @@ -28,16 +28,36 @@ class AssignTagModelsDialog extends StatefulWidget { class AssignTagModelsDialogState extends State { late List tags; late List selectedProducts; + late List locations; @override void initState() { super.initState(); + print(widget.addedProducts); + print(widget.subspaces); - if (widget.products != null) { - print(widget.products); - tags = []; - } + // Initialize tags from widget.initialTags or create new ones if it's empty + tags = widget.initialTags?.isNotEmpty == true + ? widget.initialTags! + : widget.addedProducts + .expand((selectedProduct) => List.generate( + selectedProduct.count, // Generate `count` number of tags + (index) => TagModel( + tag: '', // Initialize each tag with a default value + product: selectedProduct.product, + location: 'None', // Default location + ), + )) + .toList(); + + // Initialize selected products selectedProducts = widget.addedProducts; + + // Initialize locations from subspaces or empty list if null + locations = widget.subspaces != null + ? widget.subspaces!.map((subspace) => subspace.subspaceName).toList() + : []; + locations.add("None"); } @override @@ -47,51 +67,121 @@ class AssignTagModelsDialogState extends State { backgroundColor: ColorsManager.whiteColors, content: SingleChildScrollView( child: Container( - width: MediaQuery.of(context).size.width * 0.9, - child: DataTable( - columns: const [ - DataColumn(label: Text('#')), - DataColumn(label: Text('Device')), - DataColumn(label: Text('Tag')), - DataColumn(label: Text('Location')), - ], - rows: selectedProducts.asMap().entries.map((entry) { - final index = entry.key + 1; - final selectedProduct = entry.value; - return DataRow(cells: [ - DataCell(Text(index.toString())), - DataCell(Text(selectedProduct.productName ?? 'Unknown')), - DataCell( - DropdownButton( - value: - 'Tag 1', // Static text that matches an item in the list - onChanged: (value) { - // Handle value change if needed - }, - items: List.generate(10, (index) { - final tag = 'Tag ${index + 1}'; - return DropdownMenuItem(value: tag, child: Text(tag)); - }), - ), + width: MediaQuery.of(context).size.width * 0.4, + decoration: BoxDecoration( + border: Border.all(color: ColorsManager.dataHeaderGrey, width: 1), + borderRadius: BorderRadius.circular(20), + ), + child: Theme( + data: Theme.of(context).copyWith( + dataTableTheme: DataTableThemeData( + headingRowColor: + MaterialStateProperty.all(ColorsManager.dataHeaderGrey), + headingTextStyle: const TextStyle( + color: ColorsManager.blackColor, + fontWeight: FontWeight.bold, ), - DataCell( - DropdownButton( - value: widget.subspaces?.isNotEmpty == true - ? widget.subspaces!.first.subspaceName - : null, - onChanged: (value) { - // Handle value changes here - }, - items: widget.subspaces! - .map((subspace) => DropdownMenuItem( - value: subspace.subspaceName, - child: Text(subspace.subspaceName), - )) - .toList(), - ), - ), - ]); - }).toList(), + ), + ), + child: DataTable( + border: TableBorder.all( + color: ColorsManager.dataHeaderGrey, + width: 1, + borderRadius: BorderRadius.circular(20), + ), + columns: const [ + DataColumn(label: Text('#')), + DataColumn(label: Text('Device')), + DataColumn(label: Text('Tag')), + DataColumn(label: Text('Location')), + ], + rows: tags.asMap().entries.map((entry) { + final index = entry.key + 1; + final tag = entry.value; + + return DataRow( + cells: [ + DataCell( + Center( + child: Text(index.toString()), + ), + ), + DataCell( + Center( + child: Text(tag.product?.catName ?? 'Unknown'), + ), + ), + DataCell( + Center( + child: DropdownButton( + value: tag.tag!.isNotEmpty ? tag.tag : null, + onChanged: (value) { + setState(() { + tag.tag = value ?? ''; // Update tag value + }); + }, + items: [ + const DropdownMenuItem( + value: null, + child: Text('None'), + ), + ...List.generate(10, (index) { + final tagName = 'Tag ${index + 1}'; + return DropdownMenuItem( + value: tagName, + child: Text(tagName), + ); + }), + ], + ), + ), + ), + DataCell( + Center( + child: DropdownButtonHideUnderline( + child: DropdownButton( + alignment: AlignmentDirectional.centerEnd, + value: locations.contains(tag.location) + ? tag.location + : null, // Validate value + onChanged: (value) { + setState(() { + tag.location = + value ?? 'None'; // Update location + }); + }, + dropdownColor: Colors.white, + icon: const Icon( + Icons.arrow_drop_down, + color: Colors.black, + ), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + isExpanded: true, + items: locations + .map((location) => DropdownMenuItem( + value: location, + child: Text( + location, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: Colors.grey, + ), + ), + )) + .toList(), + ), + ), + ), + ), + ], + ); + }).toList(), + ), ), ), ), diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 0c002e7d..2b749a32 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -329,7 +329,6 @@ class SpaceManagementBloc Emitter emit, ) { final communities = List.from(previousState.communities); - for (var community in communities) { if (community.uuid == communityUuid) { @@ -367,26 +366,26 @@ class SpaceManagementBloc try { if (space.uuid != null && space.uuid!.isNotEmpty) { final response = await _api.updateSpace( - communityId: communityUuid, - spaceId: space.uuid!, - name: space.name, - parentId: space.parent?.uuid, - isPrivate: space.isPrivate, - position: space.position, - icon: space.icon, - direction: space.incomingConnection?.direction, - products: space.selectedProducts); + communityId: communityUuid, + spaceId: space.uuid!, + name: space.name, + parentId: space.parent?.uuid, + isPrivate: space.isPrivate, + position: space.position, + icon: space.icon, + direction: space.incomingConnection?.direction, + ); } else { // Call create if the space does not have a UUID final response = await _api.createSpace( - communityId: communityUuid, - name: space.name, - parentId: space.parent?.uuid, - isPrivate: space.isPrivate, - position: space.position, - icon: space.icon, - direction: space.incomingConnection?.direction, - products: space.selectedProducts); + communityId: communityUuid, + name: space.name, + parentId: space.parent?.uuid, + isPrivate: space.isPrivate, + position: space.position, + icon: space.icon, + direction: space.incomingConnection?.direction, + ); space.uuid = response?.uuid; } } catch (e) { @@ -426,7 +425,7 @@ class SpaceManagementBloc emit(SpaceManagementLoading()); try { List communities = await _api.fetchCommunities(); - + List updatedCommunities = await Future.wait( communities.map((community) async { List spaces = diff --git a/lib/pages/spaces_management/all_spaces/model/selected_product_model.dart b/lib/pages/spaces_management/all_spaces/model/selected_product_model.dart index 31d7f6f8..91314e42 100644 --- a/lib/pages/spaces_management/all_spaces/model/selected_product_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/selected_product_model.dart @@ -1,9 +1,12 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; + class SelectedProduct { final String productId; int count; final String productName; + final ProductModel? product; - SelectedProduct({required this.productId, required this.count, required this.productName}); + SelectedProduct({required this.productId, required this.count, required this.productName, this.product}); Map toJson() { return { diff --git a/lib/pages/spaces_management/all_spaces/model/space_model.dart b/lib/pages/spaces_management/all_spaces/model/space_model.dart index 7406d919..4bdf4ece 100644 --- a/lib/pages/spaces_management/all_spaces/model/space_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/space_model.dart @@ -20,7 +20,6 @@ class SpaceModel { Offset position; bool isHovered; SpaceStatus status; - List selectedProducts; String internalId; List outgoingConnections = []; // Connections from this space @@ -41,7 +40,6 @@ class SpaceModel { this.isHovered = false, this.incomingConnection, this.status = SpaceStatus.unchanged, - this.selectedProducts = const [], }) : internalId = internalId ?? const Uuid().v4(); factory SpaceModel.fromJson(Map json, @@ -85,15 +83,6 @@ class SpaceModel { icon: json['icon'] ?? Assets.location, position: Offset(json['x'] ?? 0, json['y'] ?? 0), isHovered: false, - selectedProducts: json['spaceProducts'] != null - ? (json['spaceProducts'] as List).map((product) { - return SelectedProduct( - productId: product['product']['uuid'], - count: product['productCount'], - productName: '', - ); - }).toList() - : [], ); if (json['incomingConnections'] != null && diff --git a/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart index ed764250..93a38716 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart @@ -114,7 +114,7 @@ class _AddDeviceWidgetState extends State { Widget _buildDeviceTypeTile(ProductModel product, Size size) { final selectedProduct = productCounts.firstWhere( (p) => p.productId == product.uuid, - orElse: () => SelectedProduct(productId: product.uuid, count: 0, productName: product.catName), + orElse: () => SelectedProduct(productId: product.uuid, count: 0, productName: product.catName, product: product), ); return SizedBox( @@ -143,7 +143,7 @@ class _AddDeviceWidgetState extends State { if (newCount > 0) { if (!productCounts.contains(selectedProduct)) { productCounts - .add(SelectedProduct(productId: product.uuid, count: newCount, productName: product.catName)); + .add(SelectedProduct(productId: product.uuid, count: newCount, productName: product.catName, product: product)); } else { selectedProduct.count = newCount; } diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index c11563da..a929c6fd 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -299,7 +299,7 @@ class _CommunityStructureAreaState extends State { isPrivate: false, children: [], status: SpaceStatus.newSpace, - selectedProducts: selectedProducts); + ); if (parentIndex != null && direction != null) { SpaceModel parentSpace = spaces[parentIndex]; @@ -335,14 +335,12 @@ class _CommunityStructureAreaState extends State { icon: space.icon, editSpace: space, isEdit: true, - selectedProducts: space.selectedProducts, onCreateSpace: (String name, String icon, List selectedProducts) { setState(() { // Update the space's properties space.name = name; space.icon = icon; - space.selectedProducts = selectedProducts; if (space.status != SpaceStatus.newSpace) { space.status = SpaceStatus.modified; // Mark as modified diff --git a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart index 8f9d4d4d..911494a0 100644 --- a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart @@ -2,7 +2,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model class SubspaceTemplateModel { final String? uuid; - final String subspaceName; + String subspaceName; final bool disabled; final List? tags; diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart index 820b9515..1e7c594b 100644 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -1,18 +1,21 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:uuid/uuid.dart'; class TagModel { String? uuid; - String tag; + String? tag; final ProductModel? product; String internalId; + String? location; - TagModel({ - this.uuid, - required this.tag, - this.product, - String? internalId, - }) : internalId = internalId ?? const Uuid().v4(); + TagModel( + {this.uuid, + required this.tag, + this.product, + String? internalId, + this.location}) + : internalId = internalId ?? const Uuid().v4(); factory TagModel.fromJson(Map json) { final String internalId = json['internalId'] ?? const Uuid().v4(); diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart index 1049fdce..3091d152 100644 --- a/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart @@ -13,19 +13,19 @@ class AddDeviceTypeModelBloc UpdateProductCountEvent event, Emitter> emit) { final existingProduct = state.firstWhere( (p) => p.productId == event.productId, - orElse: () => SelectedProduct(productId: event.productId, count: 0,productName: event.productName ), + orElse: () => SelectedProduct(productId: event.productId, count: 0,productName: event.productName,product: event.product ), ); if (event.count > 0) { if (!state.contains(existingProduct)) { emit([ ...state, - SelectedProduct(productId: event.productId, count: event.count, productName: event.productName) + SelectedProduct(productId: event.productId, count: event.count, productName: event.productName, product: event.product) ]); } else { final updatedList = state.map((p) { if (p.productId == event.productId) { - return SelectedProduct(productId: p.productId, count: event.count, productName: p.productName); + return SelectedProduct(productId: p.productId, count: event.count, productName: p.productName,product: p.product); } return p; }).toList(); diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart index c9594020..1d6976f8 100644 --- a/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; abstract class AddDeviceTypeModelEvent extends Equatable { @override @@ -9,8 +10,9 @@ class UpdateProductCountEvent extends AddDeviceTypeModelEvent { final String productId; final int count; final String productName; + final ProductModel product; - UpdateProductCountEvent({required this.productId, required this.count, required this.productName}); + UpdateProductCountEvent({required this.productId, required this.count, required this.productName, required this.product}); @override List get props => [productId, count]; diff --git a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart index 38159057..c2d38d0b 100644 --- a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart +++ b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart @@ -24,7 +24,11 @@ class DeviceTypeTileWidget extends StatelessWidget { Widget build(BuildContext context) { final selectedProduct = productCounts.firstWhere( (p) => p.productId == product.uuid, - orElse: () => SelectedProduct(productId: product.uuid, count: 0, productName: product.catName), + orElse: () => SelectedProduct( + productId: product.uuid, + count: 0, + productName: product.catName, + product: product), ); return Card( @@ -48,7 +52,10 @@ class DeviceTypeTileWidget extends StatelessWidget { onCountChanged: (newCount) { context.read().add( UpdateProductCountEvent( - productId: product.uuid, count: newCount,productName: product.catName), + productId: product.uuid, + count: newCount, + productName: product.catName, + product: product), ); }, ), diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index def94441..333b715e 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -167,7 +167,6 @@ class CommunitySpaceManagementApi { bool isPrivate = false, required Offset position, String? icon, - required List products, }) async { try { final body = { @@ -177,7 +176,6 @@ class CommunitySpaceManagementApi { 'y': position.dy, 'direction': direction, 'icon': icon, - 'products': products.map((product) => product.toJson()).toList(), }; if (parentId != null) { body['parentUuid'] = parentId; @@ -207,7 +205,6 @@ class CommunitySpaceManagementApi { String? direction, bool isPrivate = false, required Offset position, - required List products, }) async { try { final body = { @@ -217,7 +214,6 @@ class CommunitySpaceManagementApi { 'y': position.dy, 'direction': direction, 'icon': icon, - 'products': products.map((product) => product.toJson()).toList(), }; if (parentId != null) { body['parentUuid'] = parentId; diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 8d0aae43..eaf1e6b1 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -56,5 +56,6 @@ abstract class ColorsManager { static const Color CircleImageBackground = Color(0xFFF4F4F4); static const Color softGray = Color(0xFFD5D5D5); static const Color semiTransparentBlack = Color(0x19000000); + static const Color dataHeaderGrey = Color(0x33999999); } //background: #background: #5D5D5D; From fe8f8160ec676c91e1d50ae1fe226154c5aedcfb Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 6 Jan 2025 14:20:57 +0300 Subject: [PATCH 053/175] filter --- .../add_user_dialog/bloc/users_bloc.dart | 31 +- .../add_user_dialog/bloc/users_event.dart | 7 - .../view/popup_menu_filter.dart | 96 ++- .../users_table/bloc/user_table_bloc.dart | 95 ++- .../users_table/bloc/user_table_event.dart | 16 + .../users_table/bloc/user_table_state.dart | 9 + .../users_table/view/user_table.dart | 2 +- .../users_table/view/users_page.dart | 737 ++++++++++-------- 8 files changed, 580 insertions(+), 413 deletions(-) diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index 730825cb..c06c0969 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -30,8 +30,8 @@ class UsersBloc extends Bloc { on(_onToggleNodeExpansion); on(_onToggleNodeCheck); on(_editInvitUser); - } + void _validateBasicsStep(ValidateBasicsStep event, Emitter emit) { if (formKey.currentState?.validate() ?? false) { emit(const BasicsStepValidState()); @@ -381,7 +381,7 @@ class UsersBloc extends Bloc { // Create a list of UUIDs to mark final uuidsToMark = res.spaces.map((space) => space.uuid).toList(); // Print all IDs and mark nodes in updatedCommunities - print('Printing and marking nodes in updatedCommunities:'); + debugPrint('Printing and marking nodes in updatedCommunities:'); _printAndMarkNodes(updatedCommunities, uuidsToMark); } else { print('updatedCommunities is empty!'); @@ -391,34 +391,27 @@ class UsersBloc extends Bloc { element.type == res.roleType.toString().toLowerCase().replaceAll("_", " ")) .uuid; - print('Role ID: $roleId'); + debugPrint('Role ID: $roleId'); roleSelected = roleId; add(PermissionEvent(roleUuid: roleSelected)); emit(ChangeStatusSteps()); - } else { - // emit(UsersErrorState("User not found")); - } - } else { - // emit(UsersErrorState("Invalid user ID")); - } + } else {} + } else {} } catch (e) { print("Failed to fetch user data: $e"); - // emit(UsersErrorState("Failed to fetch user data: $e")); } } - /// Recursively print all the node IDs, including nested children. - /// Recursively print all node IDs and mark nodes as `isChecked` if their UUID exists in the list. void _printAndMarkNodes(List nodes, List uuidsToMark, [int level = 0]) { for (final node in nodes) { - // Check if the current node's UUID exists in the list of UUIDs to mark. if (uuidsToMark.contains(node.uuid)) { - node.isChecked = true; // Mark the node as checked. - print( + node.isChecked = true; + debugPrint( '${' ' * level}MATCH FOUND: Node ID: ${node.uuid}, Title: ${node.title} is marked as checked.'); } else { - print('${' ' * level}Node ID: ${node.uuid}, Title: ${node.title}'); + debugPrint( + '${' ' * level}Node ID: ${node.uuid}, Title: ${node.title}'); } if (node.children.isNotEmpty) { _printAndMarkNodes(node.children, uuidsToMark, level + 1); @@ -453,8 +446,6 @@ class UsersBloc extends Bloc { emit(ChangeStatusSteps()); } -// Existing methods that remain in the BLoC: - void _updateChildrenCheckStatus(TreeNode node, bool isChecked) { for (var child in node.children) { child.isChecked = isChecked; @@ -477,7 +468,6 @@ class UsersBloc extends Bloc { (child.children.isEmpty || _areAllChildrenChecked(child))); } -// Private helper method to find the parent of a given node. TreeNode? _findParent(List nodes, TreeNode target) { for (var node in nodes) { if (node.children.contains(target)) { @@ -490,7 +480,4 @@ class UsersBloc extends Bloc { } return null; } - - - } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart index 0f4631ff..c193bbf3 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart @@ -129,8 +129,6 @@ class UpdateNodeCheckStatus extends UsersEvent { @override List get props => [node]; } - -// Define new events class ToggleNodeHighlightEvent extends UsersEvent { final TreeNode node; @@ -161,14 +159,9 @@ class ToggleNodeCheckEvent extends UsersEvent { @override List get props => []; } - -// users_event.dart - -// 1. Extend UsersEvent class ToggleNodeCheck extends UsersEvent { final TreeNode node; - // 2. Add a constructor that takes the node to toggle ToggleNodeCheck(this.node); @override List get props => []; diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart index 02eac036..fbadd07e 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/utils/style.dart'; Future showPopUpFilterMenu({ required BuildContext context, @@ -8,20 +11,18 @@ Future showPopUpFilterMenu({ Function()? onSortZtoA, Function()? cancelButton, required Map checkboxStates, + required RelativeRect position, + // Function(String)? onTextFieldChanged, Function()? onOkPressed, List? list, }) async { - final RenderBox overlay = - Overlay.of(context).context.findRenderObject() as RenderBox; + await showMenu( context: context, - position: RelativeRect.fromLTRB( - overlay.size.width / 4, - 240, - overlay.size.width / 4, - 0, - ), + position:position, + + color: ColorsManager.whiteColors, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(10)), @@ -55,28 +56,69 @@ Future showPopUpFilterMenu({ ), const PopupMenuDivider(), const PopupMenuItem( - child: Text( - "Filter by Status", - style: TextStyle(fontWeight: FontWeight.bold), - ), - ), + child: Text( + "Filter by Status", + style: TextStyle(fontWeight: FontWeight.bold), + ) + // Container( + // decoration: containerDecoration.copyWith( + // boxShadow: [], + // borderRadius: const BorderRadius.only( + // topLeft: Radius.circular(10), topRight: Radius.circular(10))), + // child: Padding( + // padding: const EdgeInsets.all(8.0), + // child: TextFormField( + // onChanged: onTextFieldChanged, + // style: const TextStyle(color: Colors.black), + // decoration: textBoxDecoration(radios: 15)!.copyWith( + // fillColor: ColorsManager.whiteColors, + // errorStyle: const TextStyle(height: 0), + // hintStyle: context.textTheme.titleSmall?.copyWith( + // color: Colors.grey, + // fontSize: 12, + // ), + // hintText: 'Search', + // suffixIcon: SizedBox( + // child: SvgPicture.asset( + // Assets.searchIconUser, + // fit: BoxFit.none, + // ), + // ), + // ), + // // "Filter by Status", + // // style: TextStyle(fontWeight: FontWeight.bold), + // ), + // ), + // ), + ), PopupMenuItem( - child: SizedBox( + child: Container( + decoration: containerDecoration.copyWith( + boxShadow: [], + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10))), + padding: const EdgeInsets.all(10), height: 200, width: 400, - child: ListView.builder( - itemCount: list?.length ?? 0, - itemBuilder: (context, index) { - final item = list![index]; - return CheckboxListTile( - title: Text(item), - value: checkboxStates[item], - onChanged: (bool? newValue) { - checkboxStates[item] = newValue ?? false; - (context as Element).markNeedsBuild(); - }, - ); - }, + child: Container( + padding: const EdgeInsets.all(10), + color: Colors.white, + child: ListView.builder( + itemCount: list?.length ?? 0, + itemBuilder: (context, index) { + final item = list![index]; + return CheckboxListTile( + dense: true, + title: Text(item), + value: checkboxStates[item], + onChanged: (bool? newValue) { + checkboxStates[item] = newValue ?? false; + (context as Element).markNeedsBuild(); + }, + ); + }, + ), ), ), ), diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart index 82212103..cdfef350 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart @@ -19,8 +19,9 @@ class UserTableBloc extends Bloc { on(_filterUsersByRole); on(_filterUsersByJobTitle); on(_filterUsersByCreated); - on(_filterUserActevate); + on(_filterUserStatus); on(_deleteUser); + on(_filterClear); } int itemsPerPage = 10; int currentPage = 1; @@ -31,7 +32,7 @@ class UserTableBloc extends Bloc { List roleTypes = []; List jobTitle = []; List createdBy = []; - List deActivate = []; + List status = ['active', 'invited', 'disabled']; Future _getUsers(GetUsers event, Emitter emit) async { emit(UsersLoadingState()); @@ -39,8 +40,14 @@ class UserTableBloc extends Bloc { roleTypes.clear(); jobTitle.clear(); createdBy.clear(); - deActivate.clear(); + // deActivate.clear(); users = await UserPermissionApi().fetchUsers(); + + users.sort((a, b) { + final dateA = _parseDateTime(a.createdDate); + final dateB = _parseDateTime(b.createdDate); + return dateB.compareTo(dateA); + }); for (var user in users) { roleTypes.add(user.roleType.toString()); } @@ -50,20 +57,14 @@ class UserTableBloc extends Bloc { for (var user in users) { createdBy.add(user.invitedBy.toString()); } - for (var user in users) { - deActivate.add(user.status.toString()); - } + // for (var user in users) { + // deActivate.add(user.status.toString()); + // } + initialUsers = List.from(users); roleTypes = roleTypes.toSet().toList(); jobTitle = jobTitle.toSet().toList(); createdBy = createdBy.toSet().toList(); - deActivate = deActivate.toSet().toList(); - - users.sort((a, b) { - final dateA = _parseDateTime(a.createdDate); - final dateB = _parseDateTime(b.createdDate); - return dateB.compareTo(dateA); - }); - initialUsers = List.from(users); + // deActivate = deActivate.toSet().toList(); _handlePageChange(ChangePage(1), emit); emit(UsersLoadedState(users: users)); } catch (e) { @@ -127,7 +128,7 @@ class UserTableBloc extends Bloc { if (currentSortOrder == "Asc") { emit(UsersLoadingState()); currentSortOrder = ""; - users = List.from(initialUsers); + users = List.from(users); emit(UsersLoadedState(users: users)); } else { emit(UsersLoadingState()); @@ -248,39 +249,83 @@ class UserTableBloc extends Bloc { emit(UsersLoadedState(users: paginatedUsers)); } + Set selectedRoles = {}; + Set selectedJobTitles = {}; + Set selectedCreatedBy = {}; + Set selectedStatuses = {}; + void _filterUsersByRole( FilterUsersByRoleEvent event, Emitter emit) { - emit(UsersLoadingState()); + selectedRoles = event.selectedRoles.toSet(); + final filteredUsers = initialUsers.where((user) { - return event.selectedRoles.contains(user.roleType); + if (selectedRoles.isEmpty) return true; + return selectedRoles.contains(user.roleType); }).toList(); + emit(UsersLoadedState(users: filteredUsers)); } void _filterUsersByJobTitle( FilterUsersByJobEvent event, Emitter emit) { - emit(UsersLoadingState()); - final filteredUsers = users.where((user) { - return event.selectedJob.contains(user.jobTitle); + selectedJobTitles = event.selectedJob.toSet(); + + final filteredUsers = initialUsers.where((user) { + if (selectedJobTitles.isEmpty) return true; + return selectedJobTitles.contains(user.jobTitle); }).toList(); + emit(UsersLoadedState(users: filteredUsers)); } void _filterUsersByCreated( FilterUsersByCreatedEvent event, Emitter emit) { - emit(UsersLoadingState()); + selectedCreatedBy = event.selectedCreatedBy.toSet(); + final filteredUsers = initialUsers.where((user) { - return event.selectedCreatedBy.contains(user.invitedBy); + if (selectedCreatedBy.isEmpty) return true; + return selectedCreatedBy.contains(user.invitedBy); }).toList(); + emit(UsersLoadedState(users: filteredUsers)); } - void _filterUserActevate( + void _filterUserStatus( FilterUsersByDeActevateEvent event, Emitter emit) { - emit(UsersLoadingState()); + selectedStatuses = event.selectedActivate.toSet(); + final filteredUsers = initialUsers.where((user) { - return event.selectedActivate.contains(user.status); + if (selectedStatuses.isEmpty) return true; + return selectedStatuses.contains(user.status); }).toList(); + emit(UsersLoadedState(users: filteredUsers)); } + + void _resetAllFilters(Emitter emit) { + selectedRoles.clear(); + selectedJobTitles.clear(); + selectedCreatedBy.clear(); + selectedStatuses.clear(); + emit(UsersLoadedState(users: initialUsers)); + } + + void _filterClear(FilterClearEvent event, Emitter emit) { + selectedRoles.clear(); + selectedJobTitles.clear(); + selectedCreatedBy.clear(); + selectedStatuses.clear(); + emit(UsersLoadedState(users: initialUsers)); + } } + // void _filterOptions(FilterOptionsEvent event, Emitter emit) { + // try { + // final query = event.query.toLowerCase(); + // final filteredOptions = event.fullOptions + // .where((option) => option.toLowerCase().contains(query)) + // .toList(); + // emit(FilterOptionsState(filteredOptions)); + // } catch (e) { + // emit(ErrorState(e.toString())); + // } + // } \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart index dbcd9a26..a81002ad 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart @@ -119,3 +119,19 @@ class FilterUsersByDeActevateEvent extends UserTableEvent { @override List get props => [selectedActivate]; } + +class FilterOptionsEvent extends UserTableEvent { + final String query; + final List fullOptions; + + FilterOptionsEvent({required this.query, required this.fullOptions}); + + @override + List get props => [query, fullOptions]; +} + +class FilterClearEvent extends UserTableEvent { + FilterClearEvent(); + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart index 2a132947..2433f9f5 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart @@ -80,3 +80,12 @@ final class ChangeTapStatus extends UserTableState { @override List get props => [select]; } + +class FilterOptionsState extends UserTableState { + final List filteredOptions; + + FilterOptionsState(this.filteredOptions); + + @override + List get props => [filteredOptions]; +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart index 4f777ee3..813e950c 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart @@ -122,7 +122,7 @@ class _DynamicTableScreenState extends State ), if (index != 1 && index != 9 && - index != 7 && + index != 8 && index != 5) FittedBox( child: IconButton( diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart index 8d838ba2..3a9f2187 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart @@ -110,350 +110,425 @@ class UsersPage extends StatelessWidget { if (state is UsersLoadingState) { _blocRole.add(ChangePage(_blocRole.currentPage)); - return const Center(child: CircularProgressIndicator()); } else if (state is UsersLoadedState) { return Padding( padding: const EdgeInsets.all(20), - child: ListView( - shrinkWrap: true, - children: [ - Row( - children: [ - Container( - decoration: containerDecoration.copyWith( - borderRadius: const BorderRadius.all( - Radius.circular(20), - ), - ), - width: screenSize.width * 0.4, - child: TextFormField( - controller: searchController, - onChanged: (value) { - context.read().add(SearchUsers(value)); - }, - style: const TextStyle(color: Colors.black), - decoration: textBoxDecoration(radios: 15)!.copyWith( - fillColor: ColorsManager.whiteColors, - errorStyle: const TextStyle(height: 0), - hintStyle: context.textTheme.titleSmall?.copyWith( - color: Colors.grey, - fontSize: 12, - ), - hintText: 'Search', - suffixIcon: SizedBox( - child: SvgPicture.asset( - Assets.searchIconUser, - fit: BoxFit.none, - ), - ), - ), - ), - ), - const SizedBox(width: 20), - InkWell( - onTap: () { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return const AddNewUserDialog(); - }, - ).then((v) { - if (v != null) { - _blocRole.add(const GetUsers()); - } - }); - }, - child: Container( - decoration: containerWhiteDecoration, - width: screenSize.width * 0.18, - height: 50, - child: const Center( - child: Text( - 'Add New User', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w700, - color: ColorsManager.blueColor, - ), - ), - ), - ), - ), - ], - ), - const SizedBox(height: 25), - DynamicTableScreen( - onFilter: (columnIndex) { - if (columnIndex == 0) { - showNameMenu( - context: context, - isSelected: _blocRole.currentSortOrder, - aToZTap: () { - context - .read() - .add(const SortUsersByNameAsc()); - }, - zToaTap: () { - context - .read() - .add(const SortUsersByNameDesc()); - }, - ); - } - if (columnIndex == 2) { - final Map checkboxStates = { - for (var item in _blocRole.jobTitle) - item: false, // Initialize with false - }; - - showPopUpFilterMenu( - list: _blocRole.jobTitle, - context: context, - checkboxStates: checkboxStates, - onOkPressed: () { - final selectedItems = checkboxStates.entries - .where((entry) => entry.value) - .map((entry) => entry.key) - .toList(); - Navigator.of(context).pop(); - _blocRole.add(FilterUsersByJobEvent(selectedItems)); - }, - onSortAtoZ: () { - context - .read() - .add(const SortUsersByNameAsc()); - }, - onSortZtoA: () { - context - .read() - .add(const SortUsersByNameDesc()); - }, - ); - } - if (columnIndex == 3) { - final Map checkboxStates = { - for (var item in _blocRole.roleTypes) - item: false, // Initialize with false - }; - - showPopUpFilterMenu( - list: _blocRole.roleTypes, - context: context, - checkboxStates: checkboxStates, - onOkPressed: () { - final selectedItems = checkboxStates.entries - .where((entry) => entry.value) - .map((entry) => entry.key) - .toList(); - Navigator.of(context).pop(); - _blocRole.add(FilterUsersByRoleEvent(selectedItems)); - }, - onSortAtoZ: () { - context - .read() - .add(const SortUsersByNameAsc()); - }, - onSortZtoA: () { - context - .read() - .add(const SortUsersByNameDesc()); - }, - ); - } - if (columnIndex == 4) { - showDateFilterMenu( - context: context, - isSelected: _blocRole.currentSortOrderDate, - aToZTap: () { - context - .read() - .add(const DateNewestToOldestEvent()); - }, - zToaTap: () { - context - .read() - .add(const DateOldestToNewestEvent()); - }, - ); - } - if (columnIndex == 6) { - final Map checkboxStates = { - for (var item in _blocRole.createdBy) - item: false, // Initialize with false - }; - - showPopUpFilterMenu( - list: _blocRole.createdBy, - context: context, - checkboxStates: checkboxStates, - onOkPressed: () { - final selectedItems = checkboxStates.entries - .where((entry) => entry.value) - .map((entry) => entry.key) - .toList(); - Navigator.of(context).pop(); - _blocRole - .add(FilterUsersByCreatedEvent(selectedItems)); - }, - onSortAtoZ: () { - context - .read() - .add(const SortUsersByNameAsc()); - }, - onSortZtoA: () { - context - .read() - .add(const SortUsersByNameDesc()); - }, - ); - } - if (columnIndex == 8) { - showDeActivateFilterMenu( - context: context, - isSelected: _blocRole.currentSortOrderDate, - aToZTap: () { - context - .read() - .add(const DateNewestToOldestEvent()); - }, - zToaTap: () { - context - .read() - .add(const DateOldestToNewestEvent()); - }, - ); - } - }, - titles: const [ - "Full Name", - "Email Address", - "Job Title", - "Role", - "Creation Date", - "Creation Time", - "Created By", - "Status", - "De/Activate", - "Action" - ], - rows: state.users.map((user) { - return [ - Text('${user.firstName} ${user.lastName}'), - Text(user.email ?? ''), - Text(user.jobTitle ?? ''), - Text(user.roleType ?? ''), - Text(user.createdDate ?? ''), - Text(user.createdTime ?? ''), - Text(user.invitedBy), - changeIconStatus( - status: - user.isEnabled == false ? 'disabled' : user.status, - userId: user.uuid, - onTap: user.status != "invited" - ? () { - final newStatus = user.status == 'active' - ? 'disabled' - : user.status == 'disabled' - ? 'invited' - : 'active'; - context.read().add( - ChangeUserStatus( - userId: user.uuid, - newStatus: user.isEnabled == false - ? 'disabled' - : user.status)); - } - : null, - ), - status( - status: - user.isEnabled == false ? 'disabled' : user.status, - ), - Row( - children: [ - // actionButton( - // title: "Activity Log", - // onTap: () {}, - // ), - actionButton( - title: "Edit", - onTap: () { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return EditUserDialog(userId: user.uuid); - }, - ).then((v) { - if (v != null) { - if (v != null) { - _blocRole.add(const GetUsers()); - } - } - }); - }, - ), - actionButton( - title: "Delete", - onTap: () { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return DeleteUserDialog( - onTapDelete: () { - _blocRole.add( - DeleteUserEvent(user.uuid, context)); - }, - ); - }, - ).then((v) { - if (v != null) { - if (v != null) { - _blocRole.add(const GetUsers()); - } - } - }); - }, - ), - ], - ), - ]; - }).toList(), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, + child: Align( + alignment: Alignment.topCenter, + child: ListView( + shrinkWrap: true, + children: [ + Row( children: [ Container( - width: 500, - child: NumberPagination( - buttonRadius: 10, - selectedButtonColor: ColorsManager.secondaryColor, - buttonUnSelectedBorderColor: ColorsManager.grayBorder, - lastPageIcon: - const Icon(Icons.keyboard_double_arrow_right), - firstPageIcon: - const Icon(Icons.keyboard_double_arrow_left), - totalPages: - (_blocRole.users.length / _blocRole.itemsPerPage) - .ceil(), - currentPage: _blocRole.currentPage, - onPageChanged: (int pageNumber) { - _blocRole.currentPage = pageNumber; + decoration: containerDecoration.copyWith( + borderRadius: const BorderRadius.all( + Radius.circular(20), + ), + ), + width: screenSize.width * 0.4, + child: TextFormField( + controller: searchController, + onChanged: (value) { context .read() - .add(ChangePage(pageNumber)); + .add(SearchUsers(value)); }, + style: const TextStyle(color: Colors.black), + decoration: textBoxDecoration(radios: 15)!.copyWith( + fillColor: ColorsManager.whiteColors, + errorStyle: const TextStyle(height: 0), + hintStyle: context.textTheme.titleSmall?.copyWith( + color: Colors.grey, + fontSize: 12, + ), + hintText: 'Search', + suffixIcon: SizedBox( + child: SvgPicture.asset( + Assets.searchIconUser, + fit: BoxFit.none, + ), + ), + ), + ), + ), + const SizedBox(width: 20), + InkWell( + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return const AddNewUserDialog(); + }, + ).then((v) { + if (v != null) { + _blocRole.add(const GetUsers()); + } + }); + }, + child: Container( + decoration: containerWhiteDecoration, + width: screenSize.width * 0.18, + height: 50, + child: const Center( + child: Text( + 'Add New User', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: ColorsManager.blueColor, + ), + ), + ), ), ), ], ), - ), - ], + const SizedBox(height: 25), + DynamicTableScreen( + onFilter: (columnIndex) { + _blocRole.add(FilterClearEvent()); + + if (columnIndex == 0) { + showNameMenu( + context: context, + isSelected: _blocRole.currentSortOrder, + aToZTap: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + zToaTap: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + if (columnIndex == 2) { + final Map checkboxStates = { + for (var item in _blocRole.jobTitle) + item: false, // Initialize with false + }; + final RenderBox overlay = Overlay.of(context) + .context + .findRenderObject() as RenderBox; + showPopUpFilterMenu( + position: RelativeRect.fromLTRB( + overlay.size.width / 4, + 240, + overlay.size.width / 4, + 0, + ), + list: _blocRole.jobTitle, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole.add(FilterUsersByJobEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + if (columnIndex == 3) { + final Map checkboxStates = { + for (var item in _blocRole.roleTypes) + item: _blocRole.selectedRoles.contains(item), + }; + final RenderBox overlay = Overlay.of(context) + .context + .findRenderObject() as RenderBox; + showPopUpFilterMenu( + position: RelativeRect.fromLTRB( + overlay.size.width / 4, + 240, + overlay.size.width / 4, + 0, + ), + list: _blocRole.roleTypes, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + context + .read() + .add(FilterUsersByRoleEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + if (columnIndex == 4) { + showDateFilterMenu( + context: context, + isSelected: _blocRole.currentSortOrderDate, + aToZTap: () { + context + .read() + .add(const DateNewestToOldestEvent()); + }, + zToaTap: () { + context + .read() + .add(const DateOldestToNewestEvent()); + }, + ); + } + if (columnIndex == 6) { + final Map checkboxStates = { + for (var item in _blocRole.createdBy) + item: _blocRole.selectedCreatedBy.contains(item), + }; + final RenderBox overlay = Overlay.of(context) + .context + .findRenderObject() as RenderBox; + showPopUpFilterMenu( + position: RelativeRect.fromLTRB( + overlay.size.width / 1, + 240, + overlay.size.width / 4, + 0, + ), + list: _blocRole.createdBy, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole + .add(FilterUsersByCreatedEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + + if (columnIndex == 7) { + final Map checkboxStates = { + for (var item in _blocRole.status) + item: _blocRole.selectedCreatedBy.contains(item), + }; + final RenderBox overlay = Overlay.of(context) + .context + .findRenderObject() as RenderBox; + showPopUpFilterMenu( + position: RelativeRect.fromLTRB( + overlay.size.width / 0, + 240, + overlay.size.width / 4, + 0, + ), + list: _blocRole.status, + context: context, + checkboxStates: checkboxStates, + onOkPressed: () { + final selectedItems = checkboxStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + Navigator.of(context).pop(); + _blocRole + .add(FilterUsersByCreatedEvent(selectedItems)); + }, + onSortAtoZ: () { + context + .read() + .add(const SortUsersByNameAsc()); + }, + onSortZtoA: () { + context + .read() + .add(const SortUsersByNameDesc()); + }, + ); + } + if (columnIndex == 8) { + showDeActivateFilterMenu( + context: context, + isSelected: _blocRole.currentSortOrderDate, + aToZTap: () { + context + .read() + .add(const DateNewestToOldestEvent()); + }, + zToaTap: () { + context + .read() + .add(const DateOldestToNewestEvent()); + }, + ); + } + }, + titles: const [ + "Full Name", + "Email Address", + "Job Title", + "Role", + "Creation Date", + "Creation Time", + "Created By", + "Status", + "De/Activate", + "Action" + ], + rows: state.users.map((user) { + return [ + Text('${user.firstName} ${user.lastName}'), + Text(user.email ?? ''), + Text(user.jobTitle ?? ''), + Text(user.roleType ?? ''), + Text(user.createdDate ?? ''), + Text(user.createdTime ?? ''), + Text(user.invitedBy), + status( + status: user.isEnabled == false + ? 'disabled' + : user.status, + ), + changeIconStatus( + status: user.isEnabled == false + ? 'disabled' + : user.status, + userId: user.uuid, + onTap: user.status != "invited" + ? () { + final newStatus = user.status == 'active' + ? 'disabled' + : user.status == 'disabled' + ? 'invited' + : 'active'; + context.read().add( + ChangeUserStatus( + userId: user.uuid, + newStatus: user.isEnabled == false + ? 'disabled' + : user.status)); + } + : null, + ), + Row( + children: [ + // actionButton( + // title: "Activity Log", + // onTap: () {}, + // ), + actionButton( + title: "Edit", + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return EditUserDialog(userId: user.uuid); + }, + ).then((v) { + if (v != null) { + if (v != null) { + _blocRole.add(const GetUsers()); + } + } + }); + }, + ), + actionButton( + title: "Delete", + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return DeleteUserDialog( + onTapDelete: () { + _blocRole.add(DeleteUserEvent( + user.uuid, context)); + }, + ); + }, + ).then((v) { + if (v != null) { + if (v != null) { + _blocRole.add(const GetUsers()); + } + } + }); + }, + ), + ], + ), + ]; + }).toList(), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + width: 500, + child: NumberPagination( + buttonRadius: 10, + selectedButtonColor: ColorsManager.secondaryColor, + buttonUnSelectedBorderColor: + ColorsManager.grayBorder, + lastPageIcon: + const Icon(Icons.keyboard_double_arrow_right), + firstPageIcon: + const Icon(Icons.keyboard_double_arrow_left), + totalPages: (_blocRole.users.length / + _blocRole.itemsPerPage) + .ceil(), + currentPage: _blocRole.currentPage, + onPageChanged: (int pageNumber) { + _blocRole.currentPage = pageNumber; + context + .read() + .add(ChangePage(pageNumber)); + }, + ), + ), + ], + ), + ), + ], + ), ), ); } else if (state is ErrorState) { From d721f6f774c31575c212e60fce7b30999a6454ea Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 6 Jan 2025 14:42:32 +0300 Subject: [PATCH 054/175] table size --- .../users_table/bloc/user_table_bloc.dart | 2 +- .../users_page/users_table/view/user_table.dart | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart index cdfef350..174726f6 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart @@ -23,7 +23,7 @@ class UserTableBloc extends Bloc { on(_deleteUser); on(_filterClear); } - int itemsPerPage = 10; + int itemsPerPage = 20; int currentPage = 1; List users = []; List initialUsers = []; diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart index 813e950c..6662a9e2 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart @@ -29,7 +29,9 @@ class _DynamicTableScreenState extends State @override void initState() { super.initState(); - columnWidths = List.filled(widget.titles.length, 150.0); + setState(() { + columnWidths = List.filled(widget.titles.length, 150.0); + }); WidgetsBinding.instance.addObserver(this); } @@ -212,6 +214,19 @@ class _DynamicTableScreenState extends State shrinkWrap: true, itemCount: widget.rows.length, itemBuilder: (context, rowIndex) { + if (columnWidths + .every((width) => width == 120.0)) { + columnWidths = List.generate( + widget.titles.length, (index) { + if (index == 1) { + return screenWidth * 0.11; + } else if (index == 9) { + return screenWidth * 0.2; + } + return screenWidth * 0.11; + }); + setState(() {}); + } final row = widget.rows[rowIndex]; return Column( children: [ From 8fd1259f9c731659b311bafca43dcbfd75a8ae18 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 6 Jan 2025 15:15:01 +0300 Subject: [PATCH 055/175] remove warnings --- .../bloc/roles_permission_state.dart | 6 ++- .../model/edit_user_model.dart | 4 +- .../add_user_dialog/bloc/users_bloc.dart | 19 ++++------ .../add_user_dialog/bloc/users_event.dart | 38 +++++++++---------- .../add_user_dialog/bloc/users_status.dart | 13 ++++--- .../model/permission_option_model.dart | 4 +- .../add_user_dialog/view/basics_view.dart | 11 +++--- .../view/popup_menu_filter.dart | 3 -- .../view/roles_and_permission.dart | 2 - .../view/spaces_access_view.dart | 6 +-- .../users_table/bloc/user_table_bloc.dart | 19 ++-------- .../users_table/bloc/user_table_state.dart | 10 ++--- .../users_table/view/users_page.dart | 14 +++---- lib/services/user_permission.dart | 2 +- 14 files changed, 67 insertions(+), 84 deletions(-) diff --git a/lib/pages/roles_and_permission/bloc/roles_permission_state.dart b/lib/pages/roles_and_permission/bloc/roles_permission_state.dart index 55c2a8cb..7979f7db 100644 --- a/lib/pages/roles_and_permission/bloc/roles_permission_state.dart +++ b/lib/pages/roles_and_permission/bloc/roles_permission_state.dart @@ -13,6 +13,7 @@ final class RolesLoadingState extends RolesPermissionState { @override List get props => []; } + final class UsersLoadingState extends RolesPermissionState { @override List get props => []; @@ -22,6 +23,7 @@ final class RolesLoadedState extends RolesPermissionState { @override List get props => []; } + final class UsersLoadedState extends RolesPermissionState { @override List get props => []; @@ -68,9 +70,9 @@ final class SosAutomationReportErrorState extends RolesPermissionState { } final class ChangeTapStatus extends RolesPermissionState { - bool select = true; + final bool select; - ChangeTapStatus({required this.select}); + const ChangeTapStatus({required this.select}); @override List get props => [select]; diff --git a/lib/pages/roles_and_permission/model/edit_user_model.dart b/lib/pages/roles_and_permission/model/edit_user_model.dart index 17fba1a4..4075ac12 100644 --- a/lib/pages/roles_and_permission/model/edit_user_model.dart +++ b/lib/pages/roles_and_permission/model/edit_user_model.dart @@ -149,8 +149,8 @@ class EditUserModel { final String createdTime; // e.g. "8:41:43 AM" final String status; // e.g. "invited" final String invitedBy; // e.g. "SUPER_ADMIN" - final String phoneNumber; // can be empty - final String jobTitle; // can be empty + final String? phoneNumber; // can be empty + final String? jobTitle; // can be empty final String roleType; // e.g. "ADMIN" final List spaces; diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index c06c0969..563fca93 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -29,7 +29,7 @@ class UsersBloc extends Bloc { on(getUserById); on(_onToggleNodeExpansion); on(_onToggleNodeCheck); - on(_editInvitUser); + on(_editInviteUser); } void _validateBasicsStep(ValidateBasicsStep event, Emitter emit) { @@ -88,7 +88,6 @@ class UsersBloc extends Bloc { await CommunitySpaceManagementApi().fetchCommunities(); updatedCommunities = await Future.wait( communities.map((community) async { - print(community.uuid); List spaces = await _fetchSpacesForCommunity(community.uuid); spacesNodes = _buildTreeNodes(spaces); @@ -102,7 +101,7 @@ class UsersBloc extends Bloc { ); }).toList(), ); - emit(SpacesLoadedState()); + emit(const SpacesLoadedState()); return updatedCommunities; } catch (e) { emit(ErrorState('Error loading communities and spaces: $e')); @@ -112,7 +111,7 @@ class UsersBloc extends Bloc { List _buildTreeNodes(List spaces) { return spaces.map((space) { List childNodes = - space.children != null ? _buildTreeNodes(space.children) : []; + space.children.isNotEmpty ? _buildTreeNodes(space.children) : []; return TreeNode( uuid: space.uuid!, title: space.name, @@ -212,7 +211,7 @@ class UsersBloc extends Bloc { _sendInvitUser(SendInviteUsers event, Emitter emit) async { try { emit(UsersLoadingState()); - List selectedIds = getSelectedIds(updatedCommunities) ?? []; + List selectedIds = getSelectedIds(updatedCommunities); bool res = await UserPermissionApi().sendInviteUser( email: emailController.text, firstName: firstNameController.text, @@ -249,10 +248,10 @@ class UsersBloc extends Bloc { } } - _editInvitUser(EditInviteUsers event, Emitter emit) async { + _editInviteUser(EditInviteUsers event, Emitter emit) async { try { emit(UsersLoadingState()); - List selectedIds = getSelectedIds(updatedCommunities) ?? []; + List selectedIds = getSelectedIds(updatedCommunities); bool res = await UserPermissionApi().editInviteUser( userId: event.userId, firstName: firstNameController.text, @@ -383,8 +382,6 @@ class UsersBloc extends Bloc { // Print all IDs and mark nodes in updatedCommunities debugPrint('Printing and marking nodes in updatedCommunities:'); _printAndMarkNodes(updatedCommunities, uuidsToMark); - } else { - print('updatedCommunities is empty!'); } final roleId = roles .firstWhere((element) => @@ -397,9 +394,7 @@ class UsersBloc extends Bloc { emit(ChangeStatusSteps()); } else {} } else {} - } catch (e) { - print("Failed to fetch user data: $e"); - } + } catch (_) {} } void _printAndMarkNodes(List nodes, List uuidsToMark, diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart index c193bbf3..2e82168c 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart @@ -63,31 +63,31 @@ class GetBatchStatus extends UsersEvent { //isEditUser:widget.userId!=''? false:true class CheckStepStatus extends UsersEvent { final int? steps; - bool? isEditUser = false; - CheckStepStatus({this.steps, required this.isEditUser}); + final bool? isEditUser; + const CheckStepStatus({this.steps, required this.isEditUser}); @override List get props => [steps]; } class SearchAnode extends UsersEvent { - List? nodes; - String? searchTerm; - SearchAnode({this.nodes, this.searchTerm}); + final List? nodes; + final String? searchTerm; + const SearchAnode({this.nodes, this.searchTerm}); @override List get props => [nodes, searchTerm]; } class SearchPermission extends UsersEvent { - List? nodes; - String? searchTerm; - SearchPermission({this.nodes, this.searchTerm}); + final List? nodes; + final String? searchTerm; + const SearchPermission({this.nodes, this.searchTerm}); @override List get props => [nodes, searchTerm]; } -class SelecteId extends UsersEvent { - List? nodes; - SelecteId({ +class SelectedId extends UsersEvent { + final List? nodes; + const SelectedId({ this.nodes, }); @override @@ -95,7 +95,7 @@ class SelecteId extends UsersEvent { } class ValidateBasicsStep extends UsersEvent { - ValidateBasicsStep(); + const ValidateBasicsStep(); @override List get props => []; } @@ -116,7 +116,7 @@ class GetUserByIdEvent extends UsersEvent { class ToggleNodeExpansion extends UsersEvent { final TreeNode node; - ToggleNodeExpansion({required this.node}); + const ToggleNodeExpansion({required this.node}); @override List get props => [node]; @@ -125,14 +125,15 @@ class ToggleNodeExpansion extends UsersEvent { class UpdateNodeCheckStatus extends UsersEvent { final TreeNode node; - UpdateNodeCheckStatus({required this.node}); + const UpdateNodeCheckStatus({required this.node}); @override List get props => [node]; } + class ToggleNodeHighlightEvent extends UsersEvent { final TreeNode node; - ToggleNodeHighlightEvent(this.node); + const ToggleNodeHighlightEvent(this.node); @override List get props => [node]; } @@ -155,14 +156,15 @@ class ClearSelectionsEvent extends UsersEvent { class ToggleNodeCheckEvent extends UsersEvent { final TreeNode node; - ToggleNodeCheckEvent(this.node); + const ToggleNodeCheckEvent(this.node); @override List get props => []; } + class ToggleNodeCheck extends UsersEvent { final TreeNode node; - ToggleNodeCheck(this.node); + const ToggleNodeCheck(this.node); @override List get props => []; } @@ -172,5 +174,3 @@ class EditUserEvent extends UsersEvent { @override List get props => []; } - - diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart index 646dccfd..4361c37f 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart @@ -1,5 +1,4 @@ import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; sealed class UsersState extends Equatable { @@ -32,7 +31,7 @@ final class SaveState extends UsersState { } final class SpacesLoadedState extends UsersState { - SpacesLoadedState(); + const SpacesLoadedState(); @override List get props => []; } @@ -58,9 +57,9 @@ final class RolesErrorState extends UsersState { /// automation reports final class ChangeTapStatus extends UsersState { - bool select = true; + final bool select; - ChangeTapStatus({required this.select}); + const ChangeTapStatus({required this.select}); @override List get props => [select]; @@ -77,14 +76,16 @@ class BasicsStepInvalidState extends UsersState { @override List get props => []; } + final class ValidateBasics extends UsersState { @override List get props => []; } + class UsersLoadedState extends UsersState { final List updatedCommunities; - UsersLoadedState({required this.updatedCommunities}); - @override + const UsersLoadedState({required this.updatedCommunities}); + @override List get props => []; } diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart index c476ebb4..4141ccdd 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart @@ -16,7 +16,9 @@ class PermissionOption { factory PermissionOption.fromJson(Map json) { return PermissionOption( id: json['id'] ?? '', - title: json['title'].toString().toLowerCase().replaceAll("_", " ") ?? '', + title: json['title'] != null + ? json['title'].toString().toLowerCase().replaceAll("_", " ") + : '', isChecked: json['isChecked'] ?? false, isHighlighted: json['isHighlighted'] ?? false, subOptions: (json['subOptions'] as List?) diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart index bbca9aaa..c5025fc3 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart @@ -11,8 +11,8 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class BasicsView extends StatelessWidget { - String? userId = ''; - BasicsView({super.key, this.userId}); + final String? userId; + const BasicsView({super.key, this.userId = ''}); @override Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { @@ -185,11 +185,12 @@ class BasicsView extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( - enabled: userId!=''? false:true, + enabled: userId != '' ? false : true, onChanged: (value) { Future.delayed(const Duration(milliseconds: 200), () { - _blocRole.add(CheckStepStatus(isEditUser:userId!=''? false:true)); - _blocRole.add( ValidateBasicsStep()); + _blocRole.add(CheckStepStatus( + isEditUser: userId != '' ? false : true)); + _blocRole.add(ValidateBasicsStep()); }); }, controller: _blocRole.emailController, diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart index fbadd07e..7c3c1ba5 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; -import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; Future showPopUpFilterMenu({ @@ -12,7 +10,6 @@ Future showPopUpFilterMenu({ Function()? cancelButton, required Map checkboxStates, required RelativeRect position, - // Function(String)? onTextFieldChanged, Function()? onOkPressed, List? list, }) async { diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart index 0bc16a94..f4b91747 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart @@ -15,8 +15,6 @@ class RolesAndPermission extends StatelessWidget { const RolesAndPermission({super.key}); @override Widget build(BuildContext context) { - final size = MediaQuery.of(context).size; - return BlocBuilder(builder: (context, state) { final _blocRole = BlocProvider.of(context); return Container( diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart index dc2eefc3..f4ccfafc 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart @@ -11,12 +11,10 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class SpacesAccessView extends StatelessWidget { - String? userId = ''; - SpacesAccessView({super.key, this.userId}); + final String? userId; + const SpacesAccessView({super.key, this.userId = ''}); @override Widget build(BuildContext context) { - final size = MediaQuery.of(context).size; - return BlocBuilder(builder: (context, state) { final _blocRole = BlocProvider.of(context); return Container( diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart index 174726f6..aa014bd5 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart @@ -210,7 +210,7 @@ class UserTableBloc extends Bloc { final query = event.query.toLowerCase(); final filteredUsers = initialUsers.where((user) { final fullName = "${user.firstName} ${user.lastName}".toLowerCase(); - final email = user.email?.toLowerCase() ?? ""; + final email = user.email.toLowerCase() ; return fullName.contains(query) || email.contains(query); }).toList(); emit(UsersLoadedState(users: filteredUsers)); @@ -224,7 +224,7 @@ class UserTableBloc extends Bloc { final startIndex = (pageNumber - 1) * itemsPerPage; final endIndex = startIndex + itemsPerPage; if (startIndex >= users.length) { - emit(UsersLoadedState(users: const [])); + emit(const UsersLoadedState(users: [])); return; } final paginatedUsers = users.sublist( @@ -235,11 +235,11 @@ class UserTableBloc extends Bloc { } void _handlePageChange(ChangePage event, Emitter emit) { - final itemsPerPage = 10; + const itemsPerPage = 10; final startIndex = (event.pageNumber - 1) * itemsPerPage; final endIndex = startIndex + itemsPerPage; if (startIndex >= users.length) { - emit(UsersLoadedState(users: [])); + emit(const UsersLoadedState(users: [])); return; } final paginatedUsers = users.sublist( @@ -318,14 +318,3 @@ class UserTableBloc extends Bloc { emit(UsersLoadedState(users: initialUsers)); } } - // void _filterOptions(FilterOptionsEvent event, Emitter emit) { - // try { - // final query = event.query.toLowerCase(); - // final filteredOptions = event.fullOptions - // .where((option) => option.toLowerCase().contains(query)) - // .toList(); - // emit(FilterOptionsState(filteredOptions)); - // } catch (e) { - // emit(ErrorState(e.toString())); - // } - // } \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart index 2433f9f5..62037315 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart @@ -26,8 +26,8 @@ final class RolesLoadedState extends UserTableState { } final class UsersLoadedState extends UserTableState { - List users = []; - UsersLoadedState({required this.users}); + final List users; + const UsersLoadedState({required this.users}); @override List get props => [users]; } @@ -73,9 +73,9 @@ final class SosAutomationReportErrorState extends UserTableState { } final class ChangeTapStatus extends UserTableState { - bool select = true; + final bool select; - ChangeTapStatus({required this.select}); + const ChangeTapStatus({required this.select}); @override List get props => [select]; @@ -84,7 +84,7 @@ final class ChangeTapStatus extends UserTableState { class FilterOptionsState extends UserTableState { final List filteredOptions; - FilterOptionsState(this.filteredOptions); + const FilterOptionsState(this.filteredOptions); @override List get props => [filteredOptions]; diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart index 3a9f2187..1c93d44f 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart @@ -412,7 +412,7 @@ class UsersPage extends StatelessWidget { rows: state.users.map((user) { return [ Text('${user.firstName} ${user.lastName}'), - Text(user.email ?? ''), + Text(user.email ), Text(user.jobTitle ?? ''), Text(user.roleType ?? ''), Text(user.createdDate ?? ''), @@ -430,11 +430,11 @@ class UsersPage extends StatelessWidget { userId: user.uuid, onTap: user.status != "invited" ? () { - final newStatus = user.status == 'active' - ? 'disabled' - : user.status == 'disabled' - ? 'invited' - : 'active'; + // final newStatus = user.status == 'active' + // ? 'disabled' + // : user.status == 'disabled' + // ? 'invited' + // : 'active'; context.read().add( ChangeUserStatus( userId: user.uuid, @@ -501,7 +501,7 @@ class UsersPage extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - Container( + SizedBox( width: 500, child: NumberPagination( buttonRadius: 10, diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index f62437ac..ab05523f 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -131,7 +131,7 @@ class UserPermissionApi { } } - Future fetchUserById(userUuid) async { + Future fetchUserById(userUuid) async { final response = await _httpService.get( path: ApiEndpoints.getUserById.replaceAll("{userUuid}", userUuid), showServerMessage: true, From 6ee650e9f86bd44e0b64071a4aa5228ce2de442a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 6 Jan 2025 16:38:44 +0400 Subject: [PATCH 056/175] added validation --- .../views/assign_tag_models_dialog.dart | 323 +++++++++++------- .../models/space_template_model.dart | 26 +- .../space_model/view/space_model_page.dart | 36 +- .../dialog/create_space_model_dialog.dart | 4 +- .../widgets/space_model_card_widget.dart | 15 +- .../views/add_device_type_model_widget.dart | 5 +- 6 files changed, 278 insertions(+), 131 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart index 885f483b..b04e1c35 100644 --- a/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart @@ -11,15 +11,17 @@ class AssignTagModelsDialog extends StatefulWidget { final List? initialTags; final ValueChanged>? onTagsAssigned; final List addedProducts; + final List? allTags; - const AssignTagModelsDialog({ - Key? key, - required this.products, - required this.subspaces, - required this.addedProducts, - this.initialTags, - this.onTagsAssigned, - }) : super(key: key); + const AssignTagModelsDialog( + {Key? key, + required this.products, + required this.subspaces, + required this.addedProducts, + this.initialTags, + this.onTagsAssigned, + this.allTags}) + : super(key: key); @override AssignTagModelsDialogState createState() => AssignTagModelsDialogState(); @@ -29,12 +31,15 @@ class AssignTagModelsDialogState extends State { late List tags; late List selectedProducts; late List locations; + late List otherTags; + late List controllers; + Set usedTags = {}; + String? errorMessage; + bool isSaveEnabled = true; @override void initState() { super.initState(); - print(widget.addedProducts); - print(widget.subspaces); // Initialize tags from widget.initialTags or create new ones if it's empty tags = widget.initialTags?.isNotEmpty == true @@ -58,6 +63,39 @@ class AssignTagModelsDialogState extends State { ? widget.subspaces!.map((subspace) => subspace.subspaceName).toList() : []; locations.add("None"); + + otherTags = widget.allTags != null ? widget.allTags! : []; + + controllers = List.generate( + tags.length, + (index) => TextEditingController(text: tags[index].tag), + ); + + for (final tag in tags) { + if (tag.tag != null && tag.tag!.isNotEmpty) { + usedTags.add(tag.tag!); + } + } + + _validateTags(); + } + + void _validateTags() { + // Disable save if any tag is empty + final hasEmptyTag = + tags.any((tag) => tag.tag == null || tag.tag!.trim().isEmpty); + setState(() { + isSaveEnabled = !hasEmptyTag; + }); + } + + @override + void dispose() { + // Dispose of controllers + for (final controller in controllers) { + controller.dispose(); + } + super.dispose(); } @override @@ -66,123 +104,160 @@ class AssignTagModelsDialogState extends State { title: const Text('Assign Tags'), backgroundColor: ColorsManager.whiteColors, content: SingleChildScrollView( - child: Container( - width: MediaQuery.of(context).size.width * 0.4, - decoration: BoxDecoration( - border: Border.all(color: ColorsManager.dataHeaderGrey, width: 1), - borderRadius: BorderRadius.circular(20), - ), - child: Theme( - data: Theme.of(context).copyWith( - dataTableTheme: DataTableThemeData( - headingRowColor: - MaterialStateProperty.all(ColorsManager.dataHeaderGrey), - headingTextStyle: const TextStyle( - color: ColorsManager.blackColor, - fontWeight: FontWeight.bold, - ), - ), - ), - child: DataTable( - border: TableBorder.all( - color: ColorsManager.dataHeaderGrey, - width: 1, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: MediaQuery.of(context).size.width * 0.4, + decoration: BoxDecoration( + border: + Border.all(color: ColorsManager.dataHeaderGrey, width: 1), borderRadius: BorderRadius.circular(20), ), - columns: const [ - DataColumn(label: Text('#')), - DataColumn(label: Text('Device')), - DataColumn(label: Text('Tag')), - DataColumn(label: Text('Location')), - ], - rows: tags.asMap().entries.map((entry) { - final index = entry.key + 1; - final tag = entry.value; + child: DataTable( + border: TableBorder.all( + color: ColorsManager.dataHeaderGrey, + width: 1, + borderRadius: BorderRadius.circular(20), + ), + columns: const [ + DataColumn(label: Text('#')), + DataColumn(label: Text('Device')), + DataColumn(label: Text('Tag')), + DataColumn(label: Text('Location')), + ], + rows: List.generate(tags.length, (index) { + final tag = tags[index]; + final controller = controllers[index]; - return DataRow( - cells: [ - DataCell( - Center( - child: Text(index.toString()), - ), - ), - DataCell( - Center( - child: Text(tag.product?.catName ?? 'Unknown'), - ), - ), - DataCell( - Center( - child: DropdownButton( - value: tag.tag!.isNotEmpty ? tag.tag : null, - onChanged: (value) { - setState(() { - tag.tag = value ?? ''; // Update tag value - }); - }, - items: [ - const DropdownMenuItem( - value: null, - child: Text('None'), + return DataRow( + cells: [ + DataCell(Center(child: Text(index.toString()))), + DataCell( + Center(child: Text(tag.product?.name ?? 'Unknown'))), + DataCell( + Row( + children: [ + Expanded( + child: TextFormField( + controller: controller, + decoration: const InputDecoration( + hintText: 'Enter Tag', + border: InputBorder.none, + hintStyle: TextStyle( + color: Colors.grey, + fontSize: 14, + ), + ), + style: const TextStyle( + color: Colors.black, + fontSize: 14, + ), + onChanged: (value) { + setState(() { + tag.tag = value.trim(); + _validateTags(); + }); + }, + ), + ), + PopupMenuButton( + icon: const Icon(Icons.arrow_drop_down, + color: Colors.black), + onSelected: (value) { + setState(() { + if (tag.tag != null && tag.tag!.isNotEmpty) { + usedTags.remove(tag.tag!); + } + controller.text = value; + tag.tag = value; + + if (tag.tag != null && tag.tag!.isNotEmpty) { + usedTags.add(tag.tag!); + } + _validateTags(); // Validate after selection + }); + }, + color: Colors.white, + itemBuilder: (context) { + return widget.allTags! + .where((tagValue) => + !usedTags.contains(tagValue)) + .map((tagValue) { + return PopupMenuItem( + value: tagValue, + child: Text( + tagValue, + style: const TextStyle( + color: Color(0xFF5D5D5D), + fontSize: 14, + ), + ), + ); + }).toList(); + }, ), - ...List.generate(10, (index) { - final tagName = 'Tag ${index + 1}'; - return DropdownMenuItem( - value: tagName, - child: Text(tagName), - ); - }), ], ), ), - ), - DataCell( - Center( - child: DropdownButtonHideUnderline( - child: DropdownButton( - alignment: AlignmentDirectional.centerEnd, - value: locations.contains(tag.location) - ? tag.location - : null, // Validate value - onChanged: (value) { - setState(() { - tag.location = - value ?? 'None'; // Update location - }); - }, - dropdownColor: Colors.white, - icon: const Icon( - Icons.arrow_drop_down, - color: Colors.black, - ), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.black, - ), - isExpanded: true, - items: locations - .map((location) => DropdownMenuItem( - value: location, - child: Text( - location, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - color: Colors.grey, + DataCell( + Center( + child: DropdownButtonHideUnderline( + child: DropdownButton( + alignment: AlignmentDirectional.center, + value: locations.contains(tag.location) + ? tag.location + : null, + onChanged: (value) { + setState(() { + tag.location = value ?? 'None'; + }); + }, + dropdownColor: ColorsManager.whiteColors, + icon: const Icon(Icons.arrow_drop_down, + color: Colors.black), + style: const TextStyle( + fontSize: 10, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + isExpanded: true, + items: locations + .map((location) => DropdownMenuItem( + value: location, + child: Text( + location, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: Color(0xFF5D5D5D), + ), ), - ), - )) - .toList(), + )) + .toList(), + ), ), ), ), - ), - ], - ); - }).toList(), + ], + ); + }), + ), ), - ), + if (errorMessage != null) + Container( + width: MediaQuery.of(context).size.width * 0.4, + padding: const EdgeInsets.only(top: 8.0, left: 12.0), + child: Text( + errorMessage!, + style: const TextStyle( + color: Colors.red, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + ), + ], ), ), actions: [ @@ -198,15 +273,19 @@ class AssignTagModelsDialogState extends State { ), ), ElevatedButton( - onPressed: () { - Navigator.of(context).pop(); - if (widget.onTagsAssigned != null) { - widget.onTagsAssigned!(tags); - } - }, + onPressed: isSaveEnabled + ? () { + Navigator.of(context).pop(); + if (widget.onTagsAssigned != null) { + widget.onTagsAssigned!(tags); + } + } + : null, // Disable the button if validation fails child: const Text('Save'), style: ElevatedButton.styleFrom( - backgroundColor: ColorsManager.secondaryColor, + backgroundColor: isSaveEnabled + ? ColorsManager.secondaryColor + : Colors.grey, // Change button color when disabled foregroundColor: ColorsManager.whiteColors, ), ), 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 51804178..2fa99d2b 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 @@ -61,7 +61,6 @@ class SpaceTemplateModel { } } - class UpdateSubspaceTemplateModel { final String uuid; final Action action; @@ -133,3 +132,28 @@ class UpdateTagModel { }; } } + +extension SpaceTemplateExtensions on SpaceTemplateModel { + List listAllTagValues() { + final List tagValues = []; + + if (tags != null) { + tagValues.addAll( + tags!.map((tag) => tag.tag ?? '').where((tag) => tag.isNotEmpty)); + } + + if (subspaceModels != null) { + for (final subspace in subspaceModels!) { + if (subspace.tags != null) { + tagValues.addAll( + subspace.tags! + .map((tag) => tag.tag ?? '') + .where((tag) => tag.isNotEmpty), + ); + } + } + } + + return tagValues; + } +} diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 3c601e47..553f3be2 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.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/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart'; @@ -16,6 +14,7 @@ class SpaceModelPage extends StatelessWidget { @override Widget build(BuildContext context) { + final allTagValues = getAllTagValues(); return Scaffold( backgroundColor: ColorsManager.whiteColors, body: Padding( @@ -24,11 +23,11 @@ class SpaceModelPage extends StatelessWidget { //clipBehavior: Clip.none, shrinkWrap: false, physics: const AlwaysScrollableScrollPhysics(), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: 13.0, mainAxisSpacing: 13.0, - childAspectRatio: 3.5, + childAspectRatio: calculateChildAspectRatio(context), ), itemCount: spaceModels.length + 1, itemBuilder: (context, index) { @@ -38,7 +37,7 @@ class SpaceModelPage extends StatelessWidget { showDialog( context: context, builder: (BuildContext context) { - return CreateSpaceModelDialog(products: products); + return CreateSpaceModelDialog(products: products,allTags: allTagValues,); }, ); }, @@ -91,4 +90,31 @@ class SpaceModelPage extends StatelessWidget { ), ); } + + double calculateChildAspectRatio(BuildContext context) { + double screenWidth = MediaQuery.of(context).size.width; + + // Adjust the aspect ratio based on the screen width + if (screenWidth > 1600) { + return 4.0; // For large screens + } + if (screenWidth > 1200) { + return 3.2; // For large screens + } else if (screenWidth > 800) { + return 3.0; // For medium screens + } else { + return 2.0; // For small screens + } + } + + List getAllTagValues() { + final List allTags = []; + + for (final spaceModel in spaceModels) { + if (spaceModel.tags != null) { + allTags.addAll(spaceModel.listAllTagValues()); + } + } + return allTags; + } } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index c70e3c56..fce0522a 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -16,8 +16,9 @@ import '../../models/subspace_template_model.dart'; class CreateSpaceModelDialog extends StatelessWidget { final List? products; + final List? allTags; - const CreateSpaceModelDialog({Key? key, this.products}) : super(key: key); + const CreateSpaceModelDialog({Key? key, this.products, this.allTags}) : super(key: key); @override Widget build(BuildContext context) { @@ -79,6 +80,7 @@ class CreateSpaceModelDialog extends StatelessWidget { builder: (context) => AddDeviceTypeModelWidget( products: products, subspaces: subspaces, + allTags: allTags, ), ); if (result == true) { 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 92d5735d..208b0893 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 @@ -69,7 +69,8 @@ class SpaceModelCardWidget extends StatelessWidget { spacing: 3.0, runSpacing: 3.0, children: [ - for (var subspace in model.subspaceModels!.take(3)) + for (var subspace in model.subspaceModels! + .take(calculateTakeCount(context))) SubspaceChipWidget(subspace: subspace.subspaceName), ], ), @@ -126,4 +127,16 @@ class SpaceModelCardWidget extends StatelessWidget { ), ); } + + int calculateTakeCount(BuildContext context) { + double screenWidth = MediaQuery.of(context).size.width; + // Adjust the count based on the screen width + if (screenWidth > 1500) { + return 3; // For large screens + } else if (screenWidth > 1200) { + return 2; + } else { + return 1; // For smaller screens + } + } } diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index 7c9204ef..3f74ebb8 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -15,13 +15,15 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final ValueChanged>? onProductsSelected; final List? initialSelectedProducts; final List? subspaces; + final List? allTags; const AddDeviceTypeModelWidget( {super.key, this.products, this.initialSelectedProducts, this.onProductsSelected, - this.subspaces}); + this.subspaces, + this.allTags}); @override Widget build(BuildContext context) { @@ -80,6 +82,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { products: products, subspaces: subspaces, addedProducts: currentState, + allTags: allTags, ), ); } From 81345f7154c9317fb9a6073cf6da32433a55090c Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 6 Jan 2025 15:41:52 +0300 Subject: [PATCH 057/175] change model url --- .../users_page/add_user_dialog/bloc/users_bloc.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index 563fca93..26a1bcc7 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -7,8 +7,8 @@ import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialo import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; -import 'package:syncrow_web/pages/spaces_management/model/space_model.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/services/space_mana_api.dart'; import 'package:syncrow_web/services/user_permission.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; From e7e0149b3a80be86f3c700cad704c94c590a262a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 7 Jan 2025 16:30:36 +0400 Subject: [PATCH 058/175] assign tag dialog --- .../views/assign_tag_models_dialog.dart | 297 ----------------- .../bloc/assign_tag_model_bloc.dart | 125 ++++++++ .../bloc/assign_tag_model_event.dart | 55 ++++ .../bloc/assign_tag_model_state.dart | 37 +++ .../views/assign_tag_models_dialog.dart | 299 ++++++++++++++++++ .../space_model/models/tag_model.dart | 12 + .../views/add_device_type_model_widget.dart | 4 +- 7 files changed, 531 insertions(+), 298 deletions(-) delete mode 100644 lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart create mode 100644 lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart create mode 100644 lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart create mode 100644 lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart create mode 100644 lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart diff --git a/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart deleted file mode 100644 index b04e1c35..00000000 --- a/lib/pages/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart +++ /dev/null @@ -1,297 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; - -class AssignTagModelsDialog extends StatefulWidget { - final List? products; - final List? subspaces; - final List? initialTags; - final ValueChanged>? onTagsAssigned; - final List addedProducts; - final List? allTags; - - const AssignTagModelsDialog( - {Key? key, - required this.products, - required this.subspaces, - required this.addedProducts, - this.initialTags, - this.onTagsAssigned, - this.allTags}) - : super(key: key); - - @override - AssignTagModelsDialogState createState() => AssignTagModelsDialogState(); -} - -class AssignTagModelsDialogState extends State { - late List tags; - late List selectedProducts; - late List locations; - late List otherTags; - late List controllers; - Set usedTags = {}; - String? errorMessage; - bool isSaveEnabled = true; - - @override - void initState() { - super.initState(); - - // Initialize tags from widget.initialTags or create new ones if it's empty - tags = widget.initialTags?.isNotEmpty == true - ? widget.initialTags! - : widget.addedProducts - .expand((selectedProduct) => List.generate( - selectedProduct.count, // Generate `count` number of tags - (index) => TagModel( - tag: '', // Initialize each tag with a default value - product: selectedProduct.product, - location: 'None', // Default location - ), - )) - .toList(); - - // Initialize selected products - selectedProducts = widget.addedProducts; - - // Initialize locations from subspaces or empty list if null - locations = widget.subspaces != null - ? widget.subspaces!.map((subspace) => subspace.subspaceName).toList() - : []; - locations.add("None"); - - otherTags = widget.allTags != null ? widget.allTags! : []; - - controllers = List.generate( - tags.length, - (index) => TextEditingController(text: tags[index].tag), - ); - - for (final tag in tags) { - if (tag.tag != null && tag.tag!.isNotEmpty) { - usedTags.add(tag.tag!); - } - } - - _validateTags(); - } - - void _validateTags() { - // Disable save if any tag is empty - final hasEmptyTag = - tags.any((tag) => tag.tag == null || tag.tag!.trim().isEmpty); - setState(() { - isSaveEnabled = !hasEmptyTag; - }); - } - - @override - void dispose() { - // Dispose of controllers - for (final controller in controllers) { - controller.dispose(); - } - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return AlertDialog( - title: const Text('Assign Tags'), - backgroundColor: ColorsManager.whiteColors, - content: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: MediaQuery.of(context).size.width * 0.4, - decoration: BoxDecoration( - border: - Border.all(color: ColorsManager.dataHeaderGrey, width: 1), - borderRadius: BorderRadius.circular(20), - ), - child: DataTable( - border: TableBorder.all( - color: ColorsManager.dataHeaderGrey, - width: 1, - borderRadius: BorderRadius.circular(20), - ), - columns: const [ - DataColumn(label: Text('#')), - DataColumn(label: Text('Device')), - DataColumn(label: Text('Tag')), - DataColumn(label: Text('Location')), - ], - rows: List.generate(tags.length, (index) { - final tag = tags[index]; - final controller = controllers[index]; - - return DataRow( - cells: [ - DataCell(Center(child: Text(index.toString()))), - DataCell( - Center(child: Text(tag.product?.name ?? 'Unknown'))), - DataCell( - Row( - children: [ - Expanded( - child: TextFormField( - controller: controller, - decoration: const InputDecoration( - hintText: 'Enter Tag', - border: InputBorder.none, - hintStyle: TextStyle( - color: Colors.grey, - fontSize: 14, - ), - ), - style: const TextStyle( - color: Colors.black, - fontSize: 14, - ), - onChanged: (value) { - setState(() { - tag.tag = value.trim(); - _validateTags(); - }); - }, - ), - ), - PopupMenuButton( - icon: const Icon(Icons.arrow_drop_down, - color: Colors.black), - onSelected: (value) { - setState(() { - if (tag.tag != null && tag.tag!.isNotEmpty) { - usedTags.remove(tag.tag!); - } - controller.text = value; - tag.tag = value; - - if (tag.tag != null && tag.tag!.isNotEmpty) { - usedTags.add(tag.tag!); - } - _validateTags(); // Validate after selection - }); - }, - color: Colors.white, - itemBuilder: (context) { - return widget.allTags! - .where((tagValue) => - !usedTags.contains(tagValue)) - .map((tagValue) { - return PopupMenuItem( - value: tagValue, - child: Text( - tagValue, - style: const TextStyle( - color: Color(0xFF5D5D5D), - fontSize: 14, - ), - ), - ); - }).toList(); - }, - ), - ], - ), - ), - DataCell( - Center( - child: DropdownButtonHideUnderline( - child: DropdownButton( - alignment: AlignmentDirectional.center, - value: locations.contains(tag.location) - ? tag.location - : null, - onChanged: (value) { - setState(() { - tag.location = value ?? 'None'; - }); - }, - dropdownColor: ColorsManager.whiteColors, - icon: const Icon(Icons.arrow_drop_down, - color: Colors.black), - style: const TextStyle( - fontSize: 10, - fontWeight: FontWeight.w600, - color: Colors.black, - ), - isExpanded: true, - items: locations - .map((location) => DropdownMenuItem( - value: location, - child: Text( - location, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - color: Color(0xFF5D5D5D), - ), - ), - )) - .toList(), - ), - ), - ), - ), - ], - ); - }), - ), - ), - if (errorMessage != null) - Container( - width: MediaQuery.of(context).size.width * 0.4, - padding: const EdgeInsets.only(top: 8.0, left: 12.0), - child: Text( - errorMessage!, - style: const TextStyle( - color: Colors.red, - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ElevatedButton( - onPressed: () => Navigator.of(context).pop(), - child: const Text('Back'), - style: ElevatedButton.styleFrom( - backgroundColor: ColorsManager.boxColor, - foregroundColor: ColorsManager.blackColor, - ), - ), - ElevatedButton( - onPressed: isSaveEnabled - ? () { - Navigator.of(context).pop(); - if (widget.onTagsAssigned != null) { - widget.onTagsAssigned!(tags); - } - } - : null, // Disable the button if validation fails - child: const Text('Save'), - style: ElevatedButton.styleFrom( - backgroundColor: isSaveEnabled - ? ColorsManager.secondaryColor - : Colors.grey, // Change button color when disabled - foregroundColor: ColorsManager.whiteColors, - ), - ), - ], - ), - ], - ); - } -} diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart new file mode 100644 index 00000000..d51badca --- /dev/null +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart @@ -0,0 +1,125 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; + +class AssignTagModelBloc + extends Bloc { + AssignTagModelBloc() : super(AssignTagModelInitial()) { + on((event, emit) { + final tags = event.initialTags?.isNotEmpty == true + ? event.initialTags! + : event.addedProducts + .expand((selectedProduct) => List.generate( + selectedProduct.count, + (index) => TagModel( + tag: '', + product: selectedProduct.product, + location: 'None', + ), + )) + .toList(); + + emit( + AssignTagModelLoaded(tags: tags, isSaveEnabled: _validateTags(tags))); + }); + + on((event, emit) { + final currentState = state; + + if (currentState is AssignTagModelLoaded && + currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags); + tags[event.index].tag = event.tag; + emit(AssignTagModelLoaded( + tags: tags, + isSaveEnabled: _validateTags(tags), + errorMessage: _getValidationError(tags), + )); + } + }); + + on((event, emit) { + final currentState = state; + + if (currentState is AssignTagModelLoaded && + currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags); + + // Use copyWith for immutability + tags[event.index] = + tags[event.index].copyWith(location: event.location); + + emit(AssignTagModelLoaded( + tags: tags, + isSaveEnabled: _validateTags(tags), + )); + } + }); + + on((event, emit) { + final currentState = state; + + if (currentState is AssignTagModelLoaded && + currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags); + + emit(AssignTagModelLoaded( + tags: tags, + isSaveEnabled: _validateTags(tags), + errorMessage: _getValidationError(tags), + )); + } + }); + + on((event, emit) { + final currentState = state; + + if (currentState is AssignTagModelLoaded && + currentState.tags.isNotEmpty) { + final updatedTags = List.from(currentState.tags) + ..remove(event.tagToDelete); + + emit(AssignTagModelLoaded( + tags: updatedTags, + isSaveEnabled: _validateTags(updatedTags), + )); + } else { + emit(const AssignTagModelLoaded( + tags: [], + isSaveEnabled: false, + )); + } + }); + } + + bool _validateTags(List tags) { + if (tags.isEmpty) { + return false; + } + final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); + final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); + return uniqueTags.length == tags.length && !hasEmptyTag; + } + + String? _getValidationError(List tags) { + final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); + if (hasEmptyTag) return 'Tags cannot be empty.'; + final duplicateTags = tags + .map((tag) => tag.tag?.trim() ?? '') + .fold>({}, (map, tag) { + map[tag] = (map[tag] ?? 0) + 1; + return map; + }) + .entries + .where((entry) => entry.value > 1) + .map((entry) => entry.key) + .toList(); + + if (duplicateTags.isNotEmpty) { + return 'Duplicate tags found: ${duplicateTags.join(', ')}'; + } + + return null; + } +} diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart new file mode 100644 index 00000000..75c9ddc1 --- /dev/null +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart @@ -0,0 +1,55 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; + +abstract class AssignTagModelEvent extends Equatable { + const AssignTagModelEvent(); + + @override + List get props => []; +} + +class InitializeTagModels extends AssignTagModelEvent { + final List? initialTags; + final List addedProducts; + + const InitializeTagModels({ + required this.initialTags, + required this.addedProducts, + }); + + @override + List get props => [initialTags ?? [], addedProducts]; +} + +class UpdateTagModel extends AssignTagModelEvent { + final int index; + final String tag; + + const UpdateTagModel({required this.index, required this.tag}); + + @override + List get props => [index, tag]; +} + +class UpdateLocation extends AssignTagModelEvent { + final int index; + final String location; + + const UpdateLocation({required this.index, required this.location}); + + @override + List get props => [index, location]; +} + +class ValidateTagModels extends AssignTagModelEvent {} + +class DeleteTagModel extends AssignTagModelEvent { + final TagModel tagToDelete; + final List tags; + + const DeleteTagModel({required this.tagToDelete, required this.tags}); + + @override + List get props => [tagToDelete, tags]; +} diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart new file mode 100644 index 00000000..9812a293 --- /dev/null +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart @@ -0,0 +1,37 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; + +abstract class AssignTagModelState extends Equatable { + const AssignTagModelState(); + + @override + List get props => []; +} + +class AssignTagModelInitial extends AssignTagModelState {} + +class AssignTagModelLoading extends AssignTagModelState {} + +class AssignTagModelLoaded extends AssignTagModelState { + final List tags; + final bool isSaveEnabled; + final String? errorMessage; + + const AssignTagModelLoaded({ + required this.tags, + required this.isSaveEnabled, + this.errorMessage, + }); + + @override + List get props => [tags, isSaveEnabled]; +} + +class AssignTagModelError extends AssignTagModelState { + final String errorMessage; + + const AssignTagModelError(this.errorMessage); + + @override + List get props => [errorMessage]; +} diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart new file mode 100644 index 00000000..e58d75f0 --- /dev/null +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -0,0 +1,299 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class AssignTagModelsDialog extends StatelessWidget { + final List? products; + final List? subspaces; + final List? initialTags; + final ValueChanged>? onTagsAssigned; + final List addedProducts; + final List? allTags; + + const AssignTagModelsDialog({ + Key? key, + required this.products, + required this.subspaces, + required this.addedProducts, + this.initialTags, + this.onTagsAssigned, + this.allTags, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final List locations = + (subspaces ?? []).map((subspace) => subspace.subspaceName).toList(); + return BlocProvider( + create: (_) => AssignTagModelBloc() + ..add(InitializeTagModels( + initialTags: initialTags, + addedProducts: addedProducts, + )), + child: BlocBuilder( + builder: (context, state) { + if (state is AssignTagModelLoaded) { + print( + "Rebuilding UI with updated locations: ${state.tags.map((e) => e.location)}"); + + final controllers = List.generate( + state.tags.length, + (index) => TextEditingController(text: state.tags[index].tag), + ); + + return AlertDialog( + title: const Text('Assign Tags'), + backgroundColor: ColorsManager.whiteColors, + content: SingleChildScrollView( + child: Column( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(20), + child: DataTable( + headingRowColor: WidgetStateProperty.all( + ColorsManager.dataHeaderGrey), + border: TableBorder.all( + color: ColorsManager.dataHeaderGrey, + width: 1, + borderRadius: BorderRadius.circular(20), + ), + columns: [ + DataColumn( + label: Text('#', + style: + Theme.of(context).textTheme.bodyMedium)), + DataColumn( + label: Text('Device', + style: + Theme.of(context).textTheme.bodyMedium)), + DataColumn( + label: Text('Tag', + style: + Theme.of(context).textTheme.bodyMedium)), + DataColumn( + label: Text('Location', + style: + Theme.of(context).textTheme.bodyMedium)), + ], + rows: state.tags.isEmpty + ? [ + const DataRow(cells: [ + DataCell( + Center( + child: Text( + 'No Data Available', + style: TextStyle( + fontSize: 14, + color: Colors.grey, + ), + ), + ), + ), + DataCell(SizedBox()), + DataCell(SizedBox()), + DataCell(SizedBox()), + ]) + ] + : List.generate(state.tags.length, (index) { + final tag = state.tags[index]; + final controller = controllers[index]; + + return DataRow( + cells: [ + DataCell(Text(index.toString())), + DataCell( + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + tag.product?.name ?? 'Unknown', + overflow: TextOverflow.ellipsis, + )), + IconButton( + icon: const Icon(Icons.close, + color: ColorsManager.warningRed, + size: 16), + onPressed: () { + context + .read() + .add(DeleteTagModel( + tagToDelete: tag, + tags: state.tags)); + }, + tooltip: 'Delete Tag', + ) + ], + ), + ), + DataCell( + Row( + children: [ + Expanded( + child: TextFormField( + controller: controller, + onChanged: (value) { + context + .read() + .add(UpdateTagModel( + index: index, + tag: value.trim(), + )); + }, + decoration: const InputDecoration( + hintText: 'Enter Tag', + border: InputBorder.none, + ), + style: const TextStyle( + fontSize: 14, + color: Colors.black, + ), + ), + ), + SizedBox( + width: MediaQuery.of(context) + .size + .width * + 0.15, + child: PopupMenuButton( + color: ColorsManager.whiteColors, + icon: const Icon( + Icons.arrow_drop_down, + color: Colors.black), + onSelected: (value) { + controller.text = value; + context + .read() + .add(UpdateTagModel( + index: index, + tag: value, + )); + }, + itemBuilder: (context) { + return (allTags ?? []) + .where((tagValue) => !state + .tags + .map((e) => e.tag) + .contains(tagValue)) + .map((tagValue) { + return PopupMenuItem( + textStyle: const TextStyle( + color: ColorsManager + .textPrimaryColor), + value: tagValue, + child: ConstrainedBox( + constraints: + BoxConstraints( + minWidth: MediaQuery.of( + context) + .size + .width * + 0.15, + maxWidth: MediaQuery.of( + context) + .size + .width * + 0.15, + ), + child: Text( + tagValue, + overflow: TextOverflow + .ellipsis, + ), + )); + }).toList(); + }, + ), + ), + ], + ), + ), + DataCell( + DropdownButtonHideUnderline( + child: DropdownButton( + value: tag.location ?? 'None', + dropdownColor: ColorsManager + .whiteColors, // Dropdown background + style: const TextStyle( + color: Colors + .black), // Style for selected text + items: [ + const DropdownMenuItem( + value: 'None', + child: Text( + 'None', + style: TextStyle( + color: ColorsManager + .textPrimaryColor), + ), + ), + ...locations.map((location) { + return DropdownMenuItem( + value: location, + child: Text( + location, + style: const TextStyle( + color: ColorsManager + .textPrimaryColor), + ), + ); + }).toList(), + ], + onChanged: (value) { + if (value != null) { + context + .read() + .add(UpdateLocation( + index: index, + location: value, + )); + } + }, + ), + ), + ), + ], + ); + }), + ), + ), + if (state.errorMessage != null) + Text( + state.errorMessage!, + style: const TextStyle(color: Colors.red), + ), + ], + ), + ), + actions: [ + ElevatedButton( + onPressed: state.isSaveEnabled + ? () { + if (onTagsAssigned != null) { + onTagsAssigned!(state.tags); + } + Navigator.pop(context); + } + : null, + child: const Text('Save'), + ), + ], + ); + } else if (state is AssignTagModelLoading) { + return const Center(child: CircularProgressIndicator()); + } else { + return const Center(child: Text('Something went wrong.')); + } + }, + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart index 1e7c594b..1bd2dd02 100644 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -30,6 +30,18 @@ class TagModel { ); } + TagModel copyWith({ + String? tag, + ProductModel? product, + String? location, + }) { + return TagModel( + tag: tag ?? this.tag, + product: product ?? this.product, + location: location ?? this.location, + ); + } + Map toJson() { return { 'uuid': uuid, diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index 3f74ebb8..c6ee5424 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -1,7 +1,7 @@ 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/spaces_management/all_spaces/assign_tag_models/views/assign_tag_models_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; @@ -74,6 +74,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { onPressed: () async { final currentState = context.read().state; + Navigator.of(context).pop(); + if (currentState.isNotEmpty) { await showDialog( barrierDismissible: false, From 1228e5e7374f1b2d3085bbde9871cf586633bb27 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 7 Jan 2025 17:34:38 +0400 Subject: [PATCH 059/175] space model creation --- .../bloc/assign_tag_model_bloc.dart | 2 +- .../bloc/assign_tag_model_event.dart | 4 +- .../views/assign_tag_models_dialog.dart | 64 ++++++++++++++----- .../bloc/subspace_model_bloc.dart | 4 +- .../bloc/subspace_model_event.dart | 0 .../bloc/subspace_model_state.dart | 0 .../views/create_subspace_model_dialog.dart | 6 +- .../bloc/create_space_model_bloc.dart | 10 +++ .../bloc/create_space_model_event.dart | 9 +++ .../models/space_template_model.dart | 20 +++--- .../dialog/create_space_model_dialog.dart | 24 +++++-- .../widgets/subspace_model_create_widget.dart | 2 +- .../views/add_device_type_model_widget.dart | 6 +- 13 files changed, 110 insertions(+), 41 deletions(-) rename lib/pages/spaces_management/{subspace_model => create_subspace_model}/bloc/subspace_model_bloc.dart (92%) rename lib/pages/spaces_management/{subspace_model => create_subspace_model}/bloc/subspace_model_event.dart (100%) rename lib/pages/spaces_management/{subspace_model => create_subspace_model}/bloc/subspace_model_state.dart (100%) rename lib/pages/spaces_management/{subspace_model => create_subspace_model}/views/create_subspace_model_dialog.dart (96%) diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart index d51badca..42184278 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart @@ -24,7 +24,7 @@ class AssignTagModelBloc AssignTagModelLoaded(tags: tags, isSaveEnabled: _validateTags(tags))); }); - on((event, emit) { + on((event, emit) { final currentState = state; if (currentState is AssignTagModelLoaded && diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart index 75c9ddc1..697b1c2a 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart @@ -22,11 +22,11 @@ class InitializeTagModels extends AssignTagModelEvent { List get props => [initialTags ?? [], addedProducts]; } -class UpdateTagModel extends AssignTagModelEvent { +class UpdateTag extends AssignTagModelEvent { final int index; final String tag; - const UpdateTagModel({required this.index, required this.tag}); + const UpdateTag({required this.index, required this.tag}); @override List get props => [index, tag]; diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index e58d75f0..72f2b2ee 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -1,12 +1,16 @@ 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/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_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/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class AssignTagModelsDialog extends StatelessWidget { @@ -16,6 +20,7 @@ class AssignTagModelsDialog extends StatelessWidget { final ValueChanged>? onTagsAssigned; final List addedProducts; final List? allTags; + final String spaceName; const AssignTagModelsDialog({ Key? key, @@ -25,6 +30,7 @@ class AssignTagModelsDialog extends StatelessWidget { this.initialTags, this.onTagsAssigned, this.allTags, + required this.spaceName, }) : super(key: key); @override @@ -40,9 +46,6 @@ class AssignTagModelsDialog extends StatelessWidget { child: BlocBuilder( builder: (context, state) { if (state is AssignTagModelLoaded) { - print( - "Rebuilding UI with updated locations: ${state.tags.map((e) => e.location)}"); - final controllers = List.generate( state.tags.length, (index) => TextEditingController(text: state.tags[index].tag), @@ -143,7 +146,7 @@ class AssignTagModelsDialog extends StatelessWidget { onChanged: (value) { context .read() - .add(UpdateTagModel( + .add(UpdateTag( index: index, tag: value.trim(), )); @@ -172,7 +175,7 @@ class AssignTagModelsDialog extends StatelessWidget { controller.text = value; context .read() - .add(UpdateTagModel( + .add(UpdateTag( index: index, tag: value, )); @@ -274,16 +277,47 @@ class AssignTagModelsDialog extends StatelessWidget { ), ), actions: [ - ElevatedButton( - onPressed: state.isSaveEnabled - ? () { - if (onTagsAssigned != null) { - onTagsAssigned!(state.tags); - } - Navigator.pop(context); - } - : null, - child: const Text('Save'), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + const SizedBox(width: 10), + Expanded( + child: CancelButton( + label: 'Cancel', + onPressed: () => Navigator.of(context).pop(), + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + borderRadius: 10, + backgroundColor: state.isSaveEnabled + ? ColorsManager.secondaryColor + : ColorsManager.grayColor, + foregroundColor: ColorsManager.whiteColors, + onPressed: state.isSaveEnabled + ? () async { + Navigator.of(context).pop(); + + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => CreateSpaceModelDialog( + products: products, + allTags: allTags, + spaceModel: SpaceTemplateModel( + modelName: spaceName, + subspaceModels: subspaces, + tags: state.tags), + ), + ); + } + : null, + child: const Text('Save'), + ), + ), + const SizedBox(width: 10), + ], ), ], ); diff --git a/lib/pages/spaces_management/subspace_model/bloc/subspace_model_bloc.dart b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart similarity index 92% rename from lib/pages/spaces_management/subspace_model/bloc/subspace_model_bloc.dart rename to lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart index 7feadc4f..6c12ad04 100644 --- a/lib/pages/spaces_management/subspace_model/bloc/subspace_model_bloc.dart +++ b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart @@ -1,6 +1,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/subspace_model/bloc/subspace_model_event.dart'; -import 'package:syncrow_web/pages/spaces_management/subspace_model/bloc/subspace_model_state.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/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart'; diff --git a/lib/pages/spaces_management/subspace_model/bloc/subspace_model_event.dart b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart similarity index 100% rename from lib/pages/spaces_management/subspace_model/bloc/subspace_model_event.dart rename to lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart diff --git a/lib/pages/spaces_management/subspace_model/bloc/subspace_model_state.dart b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart similarity index 100% rename from lib/pages/spaces_management/subspace_model/bloc/subspace_model_state.dart rename to lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart diff --git a/lib/pages/spaces_management/subspace_model/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart similarity index 96% rename from lib/pages/spaces_management/subspace_model/views/create_subspace_model_dialog.dart rename to lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart index 54981975..d241b41e 100644 --- a/lib/pages/spaces_management/subspace_model/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_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/subspace_model/bloc/subspace_model_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/subspace_model/bloc/subspace_model_event.dart'; -import 'package:syncrow_web/pages/spaces_management/subspace_model/bloc/subspace_model_state.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/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 25d7c731..1ce8f651 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -24,6 +24,16 @@ class CreateSpaceModelBloc emit(CreateSpaceModelLoaded(_space!)); }); + on((event, emit) { + if (_space != null) { + _space = _space!.copyWith(modelName: event.name); // Use copyWith for immutability + emit(CreateSpaceModelLoaded(_space!)); + } else { + emit(CreateSpaceModelError("Space template not initialized")); + } + }); + + on((event, emit) { if (_space != null) { final updatedSpace = _space!.copyWith( diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index 6398c7ec..ccf0e3fb 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -11,6 +11,15 @@ class UpdateSpaceTemplate extends CreateSpaceModelEvent { UpdateSpaceTemplate(this.spaceTemplate); } +class UpdateSpaceTemplateName extends CreateSpaceModelEvent { + final String name; + + UpdateSpaceTemplateName({required this.name}); + + @override + List get props => [name]; +} + class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent { final List subspaces; 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 2fa99d2b..d493fae9 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 @@ -6,7 +6,7 @@ import 'package:uuid/uuid.dart'; class SpaceTemplateModel { final String? uuid; - final String modelName; + String modelName; final List? subspaceModels; final List? tags; String internalId; @@ -35,15 +35,6 @@ class SpaceTemplateModel { ); } - Map toJson() { - return { - 'uuid': uuid, - 'modelName': modelName, - 'subspaceModels': subspaceModels?.map((e) => e.toJson()).toList(), - 'tags': tags?.map((e) => e.toJson()).toList(), - }; - } - SpaceTemplateModel copyWith({ String? uuid, String? modelName, @@ -59,6 +50,15 @@ class SpaceTemplateModel { internalId: internalId ?? this.internalId, ); } + + Map toJson() { + return { + 'uuid': uuid, + 'modelName': modelName, + 'subspaceModels': subspaceModels?.map((e) => e.toJson()).toList(), + 'tags': tags?.map((e) => e.toJson()).toList(), + }; + } } class UpdateSubspaceTemplateModel { diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index fce0522a..8b46d50a 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -4,6 +4,7 @@ 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/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_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/button_content_widget.dart'; @@ -17,13 +18,17 @@ import '../../models/subspace_template_model.dart'; class CreateSpaceModelDialog extends StatelessWidget { final List? products; final List? allTags; + final SpaceTemplateModel? spaceModel; - const CreateSpaceModelDialog({Key? key, this.products, this.allTags}) : super(key: key); + const CreateSpaceModelDialog( + {Key? key, this.products, this.allTags, this.spaceModel}) + : super(key: key); @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; - List? subspaces = []; // Store subspaces here + List? subspaces = []; + final TextEditingController spaceNameController = TextEditingController(); return AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), @@ -33,6 +38,14 @@ class CreateSpaceModelDialog extends StatelessWidget { child: BlocProvider( create: (_) { final bloc = CreateSpaceModelBloc(); + if (spaceModel != null) { + bloc.add(UpdateSpaceTemplate(spaceModel!)); + } else { + bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( + modelName: '', + subspaceModels: [], + ))); + } return bloc; }, child: BlocBuilder( @@ -51,6 +64,7 @@ class CreateSpaceModelDialog extends StatelessWidget { SizedBox( width: screenWidth * 0.25, child: TextField( + controller: spaceNameController, style: const TextStyle(color: ColorsManager.blackColor), decoration: InputDecoration( filled: true, @@ -81,12 +95,10 @@ class CreateSpaceModelDialog extends StatelessWidget { products: products, subspaces: subspaces, allTags: allTags, + spaceName: spaceNameController.text, ), ); - if (result == true) { - // Handle the result if necessary - print('Devices added successfully'); - } + if (result == true) {} }, style: TextButton.styleFrom( padding: EdgeInsets.zero, diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 4706fece..402c2ac1 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/subspace_model/views/create_subspace_model_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index c6ee5424..021e5ac9 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -16,6 +16,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final List? initialSelectedProducts; final List? subspaces; final List? allTags; + final String spaceName; const AddDeviceTypeModelWidget( {super.key, @@ -23,7 +24,9 @@ class AddDeviceTypeModelWidget extends StatelessWidget { this.initialSelectedProducts, this.onProductsSelected, this.subspaces, - this.allTags}); + this.allTags, + required this.spaceName + }); @override Widget build(BuildContext context) { @@ -85,6 +88,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { subspaces: subspaces, addedProducts: currentState, allTags: allTags, + spaceName: spaceName, ), ); } From 08f322165e1482f895856059b0f7211312e2bcfc Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 7 Jan 2025 19:07:42 +0400 Subject: [PATCH 060/175] edit tag model pop up --- .../views/assign_tag_models_dialog.dart | 16 ++ .../models/space_template_model.dart | 2 +- .../models/subspace_template_model.dart | 2 +- .../dialog/create_space_model_dialog.dart | 42 ++--- .../widgets/subspace_model_create_widget.dart | 14 +- .../widgets/tag_chips_display_widget.dart | 151 ++++++++++++++++++ 6 files changed, 190 insertions(+), 37 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 72f2b2ee..c2b3ba9b 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -298,7 +298,23 @@ class AssignTagModelsDialog extends StatelessWidget { onPressed: state.isSaveEnabled ? () async { Navigator.of(context).pop(); + final assignedTags = {}; + for (var tag in state.tags) { + if (tag.location == null || + subspaces == null) { + continue; + } + for (var subspace in subspaces!) { + if (tag.location == subspace.subspaceName) { + subspace.tags ??= []; + subspace.tags!.add(tag); + assignedTags.add(tag); + break; + } + } + } + state.tags.removeWhere(assignedTags.contains); await showDialog( barrierDismissible: false, context: context, 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 d493fae9..ec2f466f 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 @@ -7,7 +7,7 @@ import 'package:uuid/uuid.dart'; class SpaceTemplateModel { final String? uuid; String modelName; - final List? subspaceModels; + List? subspaceModels; final List? tags; String internalId; diff --git a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart index 911494a0..119728db 100644 --- a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart @@ -4,7 +4,7 @@ class SubspaceTemplateModel { final String? uuid; String subspaceName; final bool disabled; - final List? tags; + List? tags; SubspaceTemplateModel({ this.uuid, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 8b46d50a..93e5a3dc 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -7,8 +7,8 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_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/button_content_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -27,9 +27,10 @@ class CreateSpaceModelDialog extends StatelessWidget { @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; - List? subspaces = []; - final TextEditingController spaceNameController = TextEditingController(); - + List? subspaces = spaceModel?.subspaceModels ?? []; + final TextEditingController spaceNameController = TextEditingController( + text: spaceModel?.modelName ?? '', + ); return AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), backgroundColor: ColorsManager.whiteColors, @@ -38,7 +39,7 @@ class CreateSpaceModelDialog extends StatelessWidget { child: BlocProvider( create: (_) { final bloc = CreateSpaceModelBloc(); - if (spaceModel != null) { + if (spaceModel != null) { bloc.add(UpdateSpaceTemplate(spaceModel!)); } else { bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( @@ -86,28 +87,13 @@ class CreateSpaceModelDialog extends StatelessWidget { const SizedBox(height: 16), SubspaceModelCreate(context, subspaces: subspaces), const SizedBox(height: 10), - TextButton( - onPressed: () async { - final result = await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => AddDeviceTypeModelWidget( - products: products, - subspaces: subspaces, - allTags: allTags, - spaceName: spaceNameController.text, - ), - ); - if (result == true) {} - }, - style: TextButton.styleFrom( - padding: EdgeInsets.zero, - ), - child: const ButtonContentWidget( - icon: Icons.add, - label: 'Add Devices', - ), - ), + TagChipDisplay(context, + screenWidth: screenWidth, + spaceModel: spaceModel, + products: products, + subspaces: subspaces, + allTags: allTags, + spaceNameController: spaceNameController), const SizedBox(height: 20), SizedBox( width: screenWidth * 0.25, diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 402c2ac1..30b84a4b 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -54,20 +54,20 @@ class SubspaceModelCreate extends StatelessWidget { ), ) : SizedBox( - width: screenWidth * 0.25, // Set the desired width + width: screenWidth * 0.25, child: Container( - padding: const EdgeInsets.all(8.0), // Add padding + padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, // Background color - borderRadius: BorderRadius.circular(15), // Rounded corners + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(15), border: Border.all( - color: ColorsManager.textFieldGreyColor, // Border color + color: ColorsManager.textFieldGreyColor, width: 3.0, // Border width ), ), child: Wrap( - spacing: 8.0, // Spacing between chips - runSpacing: 8.0, // Spacing between rows of chips + spacing: 8.0, + runSpacing: 8.0, children: [ ...subspaces.map( (subspace) => Chip( diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart new file mode 100644 index 00000000..00b2d493 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -0,0 +1,151 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class TagChipDisplay extends StatelessWidget { + final double screenWidth; + final SpaceTemplateModel? spaceModel; + final List? products; + final List? subspaces; + final List? allTags; + final TextEditingController spaceNameController; + + const TagChipDisplay( + BuildContext context, { + Key? key, + required this.screenWidth, + required this.spaceModel, + required this.products, + required this.subspaces, + required this.allTags, + required this.spaceNameController, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return (spaceModel?.subspaceModels?.isNotEmpty == true || spaceModel?.tags?.isNotEmpty == true || + spaceModel?.subspaceModels + ?.any((subspace) => subspace.tags?.isNotEmpty == true) == + true) + ? SizedBox( + width: screenWidth * 0.25, + child: Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(15), + border: Border.all( + color: ColorsManager.textFieldGreyColor, + width: 3.0, // Border width + ), + ), + child: Wrap( + spacing: 8.0, + runSpacing: 8.0, + children: [ + // Combine tags from spaceModel and subspaces + ..._groupTags([ + ...?spaceModel?.tags, + ...?spaceModel?.subspaceModels + ?.expand((subspace) => subspace.tags ?? []) + ]).entries.map( + (entry) => Chip( + avatar: SizedBox( + width: 24, + height: 24, + child: SvgPicture.asset( + entry.key.icon ?? + 'assets/default_icon.svg', // Provide a default asset path if null + fit: BoxFit.contain, + ), + ), + label: Text( + 'x${entry.value}', // Show count + style: const TextStyle( + color: ColorsManager.spaceColor, + ), + ), + backgroundColor: + Colors.white, // Chip background color + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(16), // Rounded chip + side: const BorderSide( + color: ColorsManager.spaceColor, // Border color + ), + ), + ), + ), + GestureDetector( + onTap: () async { + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AddDeviceTypeModelWidget( + products: products, + subspaces: subspaces, + allTags: allTags, + spaceName: spaceNameController.text, + ), + ); + // Edit action + }, + child: Chip( + label: const Text( + 'Edit', + style: TextStyle( + color: ColorsManager.spaceColor), // Text color + ), + backgroundColor: + Colors.white, // Background color for "Edit" + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), // Rounded chip + side: const BorderSide( + color: ColorsManager.spaceColor), // Border color + ), + ), + ), + ], + ), + ), + ) + : TextButton( + onPressed: () async { + final result = await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AddDeviceTypeModelWidget( + products: products, + subspaces: subspaces, + allTags: allTags, + spaceName: spaceNameController.text, + ), + ); + if (result == true) {} + }, + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + ), + child: const ButtonContentWidget( + icon: Icons.add, + label: 'Add Devices', + ), + ); + } + + Map _groupTags(List tags) { + final Map groupedTags = {}; + for (var tag in tags) { + if (tag.product != null) { + groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1; + } + } + return groupedTags; + } +} From 6b79254a89092f5ab8b3452407a6f9dc93eb4a85 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 8 Jan 2025 19:04:08 +0400 Subject: [PATCH 061/175] create space model --- .../sos/view/sos_batch_control_view.dart | 1 - .../bloc/assign_tag_model_bloc.dart | 56 +++++-- .../bloc/create_space_model_bloc.dart | 57 ++++++- .../bloc/create_space_model_event.dart | 19 ++- .../bloc/create_space_model_state.dart | 8 +- .../create_space_template_body_model.dart | 47 ++++++ .../models/space_template_model.dart | 13 +- .../space_model/models/tag_model.dart | 11 +- .../space_model/view/space_model_page.dart | 150 +++++++++--------- .../dialog/create_space_model_dialog.dart | 84 +++++++--- .../widgets/subspace_model_create_widget.dart | 15 +- .../widgets/tag_chips_display_widget.dart | 61 ++++++- .../views/add_device_type_model_widget.dart | 34 +++- .../widgets/scrollable_grid_view_widget.dart | 25 ++- lib/services/api/http_service.dart | 24 ++- lib/services/space_mana_api.dart | 1 - lib/services/space_model_mang_api.dart | 20 +++ lib/utils/constants/api_const.dart | 1 + 18 files changed, 477 insertions(+), 150 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart diff --git a/lib/pages/device_managment/sos/view/sos_batch_control_view.dart b/lib/pages/device_managment/sos/view/sos_batch_control_view.dart index bc66d69f..9082c8bd 100644 --- a/lib/pages/device_managment/sos/view/sos_batch_control_view.dart +++ b/lib/pages/device_managment/sos/view/sos_batch_control_view.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/sos/bloc/sos_device_bloc.dart'; class SOSBatchControlView extends StatelessWidget { diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart index 42184278..3dd5e27d 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart @@ -7,21 +7,49 @@ class AssignTagModelBloc extends Bloc { AssignTagModelBloc() : super(AssignTagModelInitial()) { on((event, emit) { - final tags = event.initialTags?.isNotEmpty == true - ? event.initialTags! - : event.addedProducts - .expand((selectedProduct) => List.generate( - selectedProduct.count, - (index) => TagModel( - tag: '', - product: selectedProduct.product, - location: 'None', - ), - )) - .toList(); + final initialTags = event.initialTags ?? []; - emit( - AssignTagModelLoaded(tags: tags, isSaveEnabled: _validateTags(tags))); + final existingTagCounts = {}; + for (var tag in initialTags) { + if (tag.product != null) { + existingTagCounts[tag.product!.uuid] = + (existingTagCounts[tag.product!.uuid] ?? 0) + 1; + } + } + + final allTags = []; + + for (var selectedProduct in event.addedProducts) { + final existingCount = existingTagCounts[selectedProduct.productId] ?? 0; + + if (selectedProduct.count == 0 || + selectedProduct.count <= existingCount) { + allTags.addAll(initialTags + .where((tag) => tag.product?.uuid == selectedProduct.productId)); + continue; + } + + final missingCount = selectedProduct.count - existingCount; + + allTags.addAll(initialTags + .where((tag) => tag.product?.uuid == selectedProduct.productId)); + + if (missingCount > 0) { + allTags.addAll(List.generate( + missingCount, + (index) => TagModel( + tag: '', + product: selectedProduct.product, + location: 'None', + ), + )); + } + } + + emit(AssignTagModelLoaded( + tags: allTags, + isSaveEnabled: _validateTags(allTags), + )); }); on((event, emit) { diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 1ce8f651..d93f68c5 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -1,13 +1,51 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/services/space_model_mang_api.dart'; class CreateSpaceModelBloc extends Bloc { SpaceTemplateModel? _space; - CreateSpaceModelBloc() : super(CreateSpaceModelInitial()) { + final SpaceModelManagementApi _api; + + CreateSpaceModelBloc(this._api) : super(CreateSpaceModelInitial()) { + on((event, emit) async { + try { + final spaceTemplate = event.spaceTemplate; + + + final tagBodyModels = + spaceTemplate.tags?.map((tag) => tag.toTagBodyModel()).toList() ?? + []; + + final subspaceTemplateBodyModels = + spaceTemplate.subspaceModels?.map((subspaceModel) { + final tagsubspaceBodyModels = subspaceModel.tags + ?.map((tag) => tag.toTagBodyModel()) + .toList() ?? + []; + return CreateSubspaceTemplateModel() + ..subspaceName = subspaceModel.subspaceName + ..tags = tagsubspaceBodyModels; + }).toList() ?? + []; + + final spaceModelBody = CreateSpaceTemplateBodyModel( + modelName: spaceTemplate.modelName, + tags: tagBodyModels, + subspaceModels: subspaceTemplateBodyModels); + + print(spaceModelBody); + final success = await _api.createSpaceModel(spaceModelBody); + } catch (e) { + print(e); + } + }); + on((event, emit) { emit(CreateSpaceModelLoading()); Future.delayed(const Duration(seconds: 1), () { @@ -25,24 +63,27 @@ class CreateSpaceModelBloc }); on((event, emit) { - if (_space != null) { - _space = _space!.copyWith(modelName: event.name); // Use copyWith for immutability - emit(CreateSpaceModelLoaded(_space!)); + final currentState = state; + + if (currentState is CreateSpaceModelLoaded) { + final updatedSpaceModel = + currentState.space.copyWith(modelName: event.name); + emit(CreateSpaceModelLoaded(updatedSpaceModel)); } else { emit(CreateSpaceModelError("Space template not initialized")); } }); - on((event, emit) { - if (_space != null) { - final updatedSpace = _space!.copyWith( + final currentState = state; + + if (currentState is CreateSpaceModelLoaded) { + final updatedSpace = currentState.space.copyWith( subspaceModels: [ ...(_space!.subspaceModels ?? []), ...event.subspaces, ], ); - _space = updatedSpace; emit(CreateSpaceModelLoaded(updatedSpace)); } else { emit(CreateSpaceModelError("Space template not initialized")); diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index ccf0e3fb..7944a8a8 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -1,7 +1,13 @@ +import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -abstract class CreateSpaceModelEvent {} +abstract class CreateSpaceModelEvent extends Equatable { + const CreateSpaceModelEvent(); + + @override + List get props => []; +} class LoadSpaceTemplate extends CreateSpaceModelEvent {} @@ -11,6 +17,17 @@ class UpdateSpaceTemplate extends CreateSpaceModelEvent { UpdateSpaceTemplate(this.spaceTemplate); } +class CreateSpaceTemplate extends CreateSpaceModelEvent { + final SpaceTemplateModel spaceTemplate; + + const CreateSpaceTemplate({ + required this.spaceTemplate, + }); + + @override + List get props => [spaceTemplate]; +} + class UpdateSpaceTemplateName extends CreateSpaceModelEvent { final String name; diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart index 1a9f52bb..c05e8744 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart @@ -1,6 +1,12 @@ +import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; -abstract class CreateSpaceModelState {} +abstract class CreateSpaceModelState extends Equatable { + const CreateSpaceModelState(); + + @override + List get props => []; +} class CreateSpaceModelInitial extends CreateSpaceModelState {} diff --git a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart new file mode 100644 index 00000000..896fe000 --- /dev/null +++ b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart @@ -0,0 +1,47 @@ +import 'dart:convert'; + +class TagBodyModel { + late String uuid; + late String tag; + late final String? productUuid; + + Map toJson() { + return { + 'uuid': uuid, + 'tag': tag, + 'productUuid': productUuid, + }; + } + + @override + String toString() { + return toJson().toString(); + } +} + +class CreateSubspaceTemplateModel { + late String subspaceName; + late List? tags; +} + +class CreateSpaceTemplateBodyModel { + final String modelName; + final List? tags; + final List? subspaceModels; + + CreateSpaceTemplateBodyModel({ + required this.modelName, + this.tags, + this.subspaceModels, + }); + + Map toJson() { + return { + 'modelName': modelName, + 'tags': tags, + 'subspaceModels': subspaceModels, + }; + } + + +} 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 ec2f466f..f62bbcf6 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 @@ -1,16 +1,20 @@ +import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart'; import 'package:uuid/uuid.dart'; -class SpaceTemplateModel { +class SpaceTemplateModel extends Equatable { final String? uuid; String modelName; List? subspaceModels; final List? tags; String internalId; + @override + List get props => [modelName, subspaceModels]; + SpaceTemplateModel({ this.uuid, String? internalId, @@ -26,9 +30,10 @@ class SpaceTemplateModel { uuid: json['uuid'] ?? '', internalId: internalId, modelName: json['modelName'] ?? '', - subspaceModels: (json['subspaceModels'] as List) - .map((item) => SubspaceTemplateModel.fromJson(item)) - .toList(), + subspaceModels: (json['subspaceModels'] as List?) + ?.map((e) => SubspaceTemplateModel.fromJson(e)) + .toList() ?? + [], tags: (json['tags'] as List) .map((item) => TagModel.fromJson(item)) .toList(), diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart index 1bd2dd02..99008e76 100644 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -1,5 +1,5 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:uuid/uuid.dart'; class TagModel { @@ -50,3 +50,12 @@ class TagModel { }; } } + +extension TagModelExtensions on TagModel { + TagBodyModel toTagBodyModel() { + return TagBodyModel() + ..uuid = uuid ?? '' + ..tag = tag ?? '' + ..productUuid = product?.uuid; + } +} \ No newline at end of file diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 553f3be2..6fecd125 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -19,73 +19,83 @@ class SpaceModelPage extends StatelessWidget { backgroundColor: ColorsManager.whiteColors, body: Padding( padding: const EdgeInsets.fromLTRB(20.0, 16.0, 16.0, 16.0), - child: GridView.builder( - //clipBehavior: Clip.none, - shrinkWrap: false, - physics: const AlwaysScrollableScrollPhysics(), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 13.0, - mainAxisSpacing: 13.0, - childAspectRatio: calculateChildAspectRatio(context), - ), - itemCount: spaceModels.length + 1, - itemBuilder: (context, index) { - if (index == spaceModels.length) { - return GestureDetector( - onTap: () { - showDialog( - context: context, - builder: (BuildContext context) { - return CreateSpaceModelDialog(products: products,allTags: allTagValues,); - }, - ); - }, - child: Container( - decoration: BoxDecoration( - color: ColorsManager.whiteColors, - borderRadius: BorderRadius.circular(20), - boxShadow: const [ - BoxShadow( - color: ColorsManager.semiTransparentBlackColor, - blurRadius: 15, - offset: Offset(0, 4), - spreadRadius: 0, - ), - BoxShadow( - color: ColorsManager.semiTransparentBlackColor, - blurRadius: 25, - offset: Offset(0, 15), - spreadRadius: -5, - ), - ], - ), - child: Center( - child: Container( - width: 60, - height: 60, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: ColorsManager.neutralGray, - border: Border.all( - color: ColorsManager.textFieldGreyColor, - width: 2.0, - ), - ), - child: const Icon( - Icons.add, - size: 40, - color: ColorsManager.spaceColor, // Icon color - ), - ), - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 10.0, + mainAxisSpacing: 10.0, + childAspectRatio: calculateChildAspectRatio(context), ), - ); - } + itemCount: spaceModels.length + 1, + itemBuilder: (context, index) { + if (index == spaceModels.length) { + return GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return CreateSpaceModelDialog( + products: products, + allTags: allTagValues, + ); + }, + ); + }, + child: _buildAddContainer(), + ); + } + final model = spaceModels[index]; + return SpaceModelCardWidget(model: model); + }, + ), + ), + ], + ), + ), + ); + } - final model = spaceModels[index]; - return SpaceModelCardWidget(model: model); - }, + Widget _buildAddContainer() { + return Container( + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(20), + boxShadow: const [ + BoxShadow( + color: ColorsManager.semiTransparentBlackColor, + blurRadius: 15, + offset: Offset(0, 4), + spreadRadius: 0, + ), + BoxShadow( + color: ColorsManager.semiTransparentBlackColor, + blurRadius: 25, + offset: Offset(0, 15), + spreadRadius: -5, + ), + ], + ), + child: Center( + child: Container( + width: 60, + height: 60, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.neutralGray, + border: Border.all( + color: ColorsManager.textFieldGreyColor, + width: 2.0, + ), + ), + child: const Icon( + Icons.add, + size: 40, + color: ColorsManager.spaceColor, + ), ), ), ); @@ -93,17 +103,15 @@ class SpaceModelPage extends StatelessWidget { double calculateChildAspectRatio(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; - - // Adjust the aspect ratio based on the screen width if (screenWidth > 1600) { - return 4.0; // For large screens + return 3; } if (screenWidth > 1200) { - return 3.2; // For large screens + return 5; } else if (screenWidth > 800) { - return 3.0; // For medium screens + return 5; } else { - return 2.0; // For small screens + return 6.0; } } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 93e5a3dc..bd988782 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -7,10 +7,10 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_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/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart'; +import 'package:syncrow_web/services/space_model_mang_api.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import '../../models/subspace_template_model.dart'; @@ -26,31 +26,45 @@ class CreateSpaceModelDialog extends StatelessWidget { @override Widget build(BuildContext context) { + final SpaceModelManagementApi _spaceModelApi = SpaceModelManagementApi(); + final screenWidth = MediaQuery.of(context).size.width; List? subspaces = spaceModel?.subspaceModels ?? []; final TextEditingController spaceNameController = TextEditingController( text: spaceModel?.modelName ?? '', ); + return AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), backgroundColor: ColorsManager.whiteColors, content: SizedBox( - width: screenWidth * 0.3, - child: BlocProvider( - create: (_) { - final bloc = CreateSpaceModelBloc(); - if (spaceModel != null) { - bloc.add(UpdateSpaceTemplate(spaceModel!)); - } else { - bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( - modelName: '', - subspaceModels: [], - ))); - } - return bloc; - }, - child: BlocBuilder( - builder: (context, state) { + width: screenWidth * 0.3, + child: BlocProvider( + create: (_) { + final bloc = CreateSpaceModelBloc(_spaceModelApi); + if (spaceModel != null) { + bloc.add(UpdateSpaceTemplate(spaceModel!)); + } else { + bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( + modelName: '', + subspaceModels: [], + ))); + } + + spaceNameController.addListener(() { + bloc.add(UpdateSpaceTemplateName(name: spaceNameController.text)); + }); + + return bloc; + }, + child: BlocBuilder( + builder: (context, state) { + if (state is CreateSpaceModelLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is CreateSpaceModelLoaded) { + final updatedSpaceModel = state.space; + final subspaces = updatedSpaceModel.subspaceModels ?? []; + return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -66,6 +80,11 @@ class CreateSpaceModelDialog extends StatelessWidget { width: screenWidth * 0.25, child: TextField( controller: spaceNameController, + onChanged: (value) { + context + .read() + .add(UpdateSpaceTemplateName(name: value)); + }, style: const TextStyle(color: ColorsManager.blackColor), decoration: InputDecoration( filled: true, @@ -85,11 +104,12 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ), const SizedBox(height: 16), - SubspaceModelCreate(context, subspaces: subspaces), + SubspaceModelCreate(context, + subspaces: state.space.subspaceModels ?? []), const SizedBox(height: 10), TagChipDisplay(context, screenWidth: screenWidth, - spaceModel: spaceModel, + spaceModel: updatedSpaceModel, products: products, subspaces: subspaces, allTags: allTags, @@ -109,8 +129,14 @@ class CreateSpaceModelDialog extends StatelessWidget { Expanded( child: DefaultButton( onPressed: () { - // Return data when OK is pressed - Navigator.of(context).pop(subspaces); + final updatedSpaceTemplate = + updatedSpaceModel.copyWith( + modelName: spaceNameController.text.trim(), + ); + context.read().add( + CreateSpaceTemplate( + spaceTemplate: updatedSpaceTemplate), + ); }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, @@ -123,9 +149,19 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ], ); - }, - ), - )), + } else if (state is CreateSpaceModelError) { + return Text( + 'Error: ${state.message}', + style: const TextStyle(color: Colors.red), + ); + } + + // Default case (e.g., CreateSpaceModelInitial) + return const Center(child: Text('Initializing...')); + }, + ), + ), + ), ); } } diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 30b84a4b..35b680a0 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -10,7 +10,8 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac class SubspaceModelCreate extends StatelessWidget { final List subspaces; - const SubspaceModelCreate(BuildContext context, { + const SubspaceModelCreate( + BuildContext context, { Key? key, required this.subspaces, }) : super(key: key); @@ -26,6 +27,8 @@ class SubspaceModelCreate extends StatelessWidget { overlayColor: ColorsManager.transparentColor, ), onPressed: () async { + Navigator.of(context).pop(); + final result = await showDialog>( barrierDismissible: false, context: context, @@ -58,16 +61,16 @@ class SubspaceModelCreate extends StatelessWidget { child: Container( padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, - borderRadius: BorderRadius.circular(15), + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(15), border: Border.all( - color: ColorsManager.textFieldGreyColor, + color: ColorsManager.textFieldGreyColor, width: 3.0, // Border width ), ), child: Wrap( - spacing: 8.0, - runSpacing: 8.0, + spacing: 8.0, + runSpacing: 8.0, children: [ ...subspaces.map( (subspace) => Chip( diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 00b2d493..f60e1cf3 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; @@ -29,7 +30,8 @@ class TagChipDisplay extends StatelessWidget { @override Widget build(BuildContext context) { - return (spaceModel?.subspaceModels?.isNotEmpty == true || spaceModel?.tags?.isNotEmpty == true || + return (spaceModel?.subspaceModels?.isNotEmpty == true || + spaceModel?.tags?.isNotEmpty == true || spaceModel?.subspaceModels ?.any((subspace) => subspace.tags?.isNotEmpty == true) == true) @@ -60,8 +62,7 @@ class TagChipDisplay extends StatelessWidget { width: 24, height: 24, child: SvgPicture.asset( - entry.key.icon ?? - 'assets/default_icon.svg', // Provide a default asset path if null + entry.key.icon ?? 'assets/icons/gateway.svg', fit: BoxFit.contain, ), ), @@ -71,19 +72,19 @@ class TagChipDisplay extends StatelessWidget { color: ColorsManager.spaceColor, ), ), - backgroundColor: - Colors.white, // Chip background color + backgroundColor: Colors.white, shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(16), // Rounded chip + borderRadius: BorderRadius.circular(16), side: const BorderSide( - color: ColorsManager.spaceColor, // Border color + color: ColorsManager.spaceColor, ), ), ), ), GestureDetector( onTap: () async { + Navigator.of(context).pop(); + await showDialog( barrierDismissible: false, context: context, @@ -92,6 +93,10 @@ class TagChipDisplay extends StatelessWidget { subspaces: subspaces, allTags: allTags, spaceName: spaceNameController.text, + spaceTagModels: spaceModel?.tags, + initialSelectedProducts: + _createInitialSelectedProducts( + spaceModel?.tags, spaceModel?.subspaceModels), ), ); // Edit action @@ -117,6 +122,8 @@ class TagChipDisplay extends StatelessWidget { ) : TextButton( onPressed: () async { + Navigator.of(context).pop(); + final result = await showDialog( barrierDismissible: false, context: context, @@ -148,4 +155,42 @@ class TagChipDisplay extends StatelessWidget { } return groupedTags; } + + List _createInitialSelectedProducts( + List? tags, List? subspaces) { + final Map productCounts = {}; + + // Count products in spaceModel tags + if (tags != null) { + for (var tag in tags) { + if (tag.product != null) { + productCounts[tag.product!] = (productCounts[tag.product!] ?? 0) + 1; + } + } + } + + // Count products in subspaces + if (subspaces != null) { + for (var subspace in subspaces) { + if (subspace.tags != null) { + for (var tag in subspace.tags!) { + if (tag.product != null) { + productCounts[tag.product!] = + (productCounts[tag.product!] ?? 0) + 1; + } + } + } + } + } + + // Create SelectedProduct instances + return productCounts.entries + .map((entry) => SelectedProduct( + productId: entry.key.uuid, + count: entry.value, + productName: entry.key.name ?? 'Unnamed', + product: entry.key, + )) + .toList(); + } } diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index 021e5ac9..74f11194 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assi import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart'; @@ -15,6 +16,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final ValueChanged>? onProductsSelected; final List? initialSelectedProducts; final List? subspaces; + final List? spaceTagModels; final List? allTags; final String spaceName; @@ -25,8 +27,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { this.onProductsSelected, this.subspaces, this.allTags, - required this.spaceName - }); + this.spaceTagModels, + required this.spaceName}); @override Widget build(BuildContext context) { @@ -89,6 +91,9 @@ class AddDeviceTypeModelWidget extends StatelessWidget { addedProducts: currentState, allTags: allTags, spaceName: spaceName, + initialTags: generateInitialTags( + spaceTagModels: spaceTagModels, + subspaces: subspaces), ), ); } @@ -100,4 +105,29 @@ class AddDeviceTypeModelWidget extends StatelessWidget { ), )); } + + List generateInitialTags({ + List? spaceTagModels, + List? subspaces, + }) { + final List initialTags = []; + + if (spaceTagModels != null) { + initialTags.addAll(spaceTagModels); + } + + if (subspaces != null) { + for (var subspace in subspaces) { + if (subspace.tags != null) { + initialTags.addAll( + subspace.tags!.map( + (tag) => tag.copyWith(location: subspace.subspaceName), + ), + ); + } + } + } + + return initialTags; + } } diff --git a/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart b/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart index e05c6711..2a653dde 100644 --- a/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart +++ b/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart @@ -8,11 +8,13 @@ import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_typ class ScrollableGridViewWidget extends StatelessWidget { final List? products; final int crossAxisCount; + final List? initialProductCounts; const ScrollableGridViewWidget({ super.key, required this.products, required this.crossAxisCount, + this.initialProductCounts, }); @override @@ -36,9 +38,13 @@ class ScrollableGridViewWidget extends StatelessWidget { itemCount: products?.length ?? 0, itemBuilder: (context, index) { final product = products![index]; + final initialProductCount = _findInitialProductCount(product); + return DeviceTypeTileWidget( product: product, - productCounts: productCounts, + productCounts: initialProductCount != null + ? [...productCounts, initialProductCount] + : productCounts, ); }, ); @@ -46,4 +52,21 @@ class ScrollableGridViewWidget extends StatelessWidget { ), ); } + + SelectedProduct? _findInitialProductCount(ProductModel product) { + // Check if the product exists in initialProductCounts + if (initialProductCounts == null) return null; + final matchingProduct = initialProductCounts!.firstWhere( + (selectedProduct) => selectedProduct.productId == product.uuid, + orElse: () => SelectedProduct( + productId: '', + count: 0, + productName: '', + product: null, + ), + ); + + // Check if the product was actually found + return matchingProduct.productId.isNotEmpty ? matchingProduct : null; + } } diff --git a/lib/services/api/http_service.dart b/lib/services/api/http_service.dart index b75f05cf..2ed3da42 100644 --- a/lib/services/api/http_service.dart +++ b/lib/services/api/http_service.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:dio/dio.dart'; import 'package:syncrow_web/services/api/http_interceptor.dart'; import 'package:syncrow_web/services/locator.dart'; @@ -42,22 +44,30 @@ class HTTPService { } } - Future post( - {required String path, - Map? queryParameters, - Options? options, - dynamic body, - bool showServerMessage = true, - required T Function(dynamic) expectedResponseModel}) async { + Future post({ + required String path, + Map? queryParameters, + Options? options, + dynamic body, + bool showServerMessage = true, + required T Function(dynamic) expectedResponseModel, + }) async { try { + final bodyString = body is Map || body is List + ? jsonEncode(body) + : body?.toString() ?? 'null'; + + print("POST Request: $path, Body: $bodyString, Query: $queryParameters"); final response = await client.post( path, data: body, queryParameters: queryParameters, options: options, ); + print("POST Response: ${response.data.toString()}"); return expectedResponseModel(response.data); } catch (error) { + print("POST Error: $error"); rethrow; } } diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 333b715e..da70d46f 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart index 762f9178..82ed097f 100644 --- a/lib/services/space_model_mang_api.dart +++ b/lib/services/space_model_mang_api.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; 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'; @@ -34,4 +35,23 @@ class SpaceModelManagementApi { return []; } } + + Future createSpaceModel( + CreateSpaceTemplateBodyModel spaceModel) async { + try { + final response = await HTTPService().post( + path: ApiEndpoints.createSpaceModel + .replaceAll('{projectId}', TempConst.projectId), + showServerMessage: true, + body: spaceModel.toJson(), + expectedResponseModel: (json) { + return SpaceTemplateModel.fromJson(json['data']); + }, + ); + return response; + } catch (e) { + debugPrint('Error creating community: $e'); + return null; + } + } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 2fdca23a..ca4527fb 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -99,4 +99,5 @@ abstract class ApiEndpoints { //space model static const String listSpaceModels = '/projects/{projectId}/space-models'; + static const String createSpaceModel = '/projects/{projectId}/space-models'; } From 9f5e9af5fa42fab99a0cd0d6ddf2b98064f636b6 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 8 Jan 2025 19:56:42 +0400 Subject: [PATCH 062/175] fixed edit dialog --- .../views/assign_tag_models_dialog.dart | 21 ++++++-- .../views/create_subspace_model_dialog.dart | 49 +++++++++++++++++-- .../dialog/create_space_model_dialog.dart | 12 +++-- .../widgets/subspace_model_create_widget.dart | 49 ++++++++++--------- .../widgets/tag_chips_display_widget.dart | 6 +-- .../views/add_device_type_model_widget.dart | 31 ++++++++++-- 6 files changed, 127 insertions(+), 41 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index c2b3ba9b..182fbe55 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -21,6 +21,7 @@ class AssignTagModelsDialog extends StatelessWidget { final List addedProducts; final List? allTags; final String spaceName; + final String title; const AssignTagModelsDialog({ Key? key, @@ -31,6 +32,7 @@ class AssignTagModelsDialog extends StatelessWidget { this.onTagsAssigned, this.allTags, required this.spaceName, + required this.title }) : super(key: key); @override @@ -52,7 +54,7 @@ class AssignTagModelsDialog extends StatelessWidget { ); return AlertDialog( - title: const Text('Assign Tags'), + title: const Text(title), backgroundColor: ColorsManager.whiteColors, content: SingleChildScrollView( child: Column( @@ -284,7 +286,21 @@ class AssignTagModelsDialog extends StatelessWidget { Expanded( child: CancelButton( label: 'Cancel', - onPressed: () => Navigator.of(context).pop(), + onPressed: () async { + Navigator.of(context).pop(); + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => CreateSpaceModelDialog( + products: products, + allTags: allTags, + spaceModel: SpaceTemplateModel( + modelName: spaceName, + subspaceModels: subspaces, + tags: initialTags), + ), + ); + }, ), ), const SizedBox(width: 10), @@ -313,7 +329,6 @@ class AssignTagModelsDialog extends StatelessWidget { } } } - state.tags.removeWhere(assignedTags.contains); await showDialog( barrierDismissible: false, 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 d241b41e..4a290617 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 @@ -2,22 +2,36 @@ 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/all_spaces/model/product_model.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/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateSubSpaceModelDialog extends StatelessWidget { final bool isEdit; final String dialogTitle; final List? existingSubSpaces; + final String? spaceName; + final List? spaceTagModels; + final List? allTags; + final List? products; + final SpaceTemplateModel? spaceModel; const CreateSubSpaceModelDialog({ Key? key, required this.isEdit, required this.dialogTitle, this.existingSubSpaces, + required this.allTags, + required this.spaceName, + required this.spaceTagModels, + required this.products, + required this.spaceModel, }) : super(key: key); @override @@ -153,20 +167,49 @@ class CreateSubSpaceModelDialog extends StatelessWidget { Expanded( child: CancelButton( label: 'Cancel', - onPressed: () { + onPressed: () async { Navigator.of(context).pop(); + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => + CreateSpaceModelDialog( + products: products, + allTags: allTags, + spaceModel: SpaceTemplateModel( + modelName: spaceName ?? '', + subspaceModels: existingSubSpaces, + tags: spaceTagModels, + ), + ), + ); }, ), ), const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: () { + onPressed: () async { final subSpaces = context .read() .state .subSpaces; - Navigator.of(context).pop(subSpaces); + Navigator.of(context).pop(); + + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => + CreateSpaceModelDialog( + products: products, + allTags: allTags, + spaceModel: SpaceTemplateModel( + modelName: spaceName ?? '', + subspaceModels: subSpaces, + tags: spaceTagModels, + ), + ), + ); }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index bd988782..25a8fb63 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -29,7 +29,6 @@ class CreateSpaceModelDialog extends StatelessWidget { final SpaceModelManagementApi _spaceModelApi = SpaceModelManagementApi(); final screenWidth = MediaQuery.of(context).size.width; - List? subspaces = spaceModel?.subspaceModels ?? []; final TextEditingController spaceNameController = TextEditingController( text: spaceModel?.modelName ?? '', ); @@ -104,8 +103,15 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ), const SizedBox(height: 16), - SubspaceModelCreate(context, - subspaces: state.space.subspaceModels ?? []), + SubspaceModelCreate( + context, + subspaces: state.space.subspaceModels ?? [], + allTags: allTags, + products: products, + spaceModel: spaceModel, + spaceTagModels: spaceModel?.tags ?? [], + spaceNameController: spaceNameController + ), const SizedBox(height: 10), TagChipDisplay(context, screenWidth: screenWidth, diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 35b680a0..31ed8659 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -1,25 +1,34 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; class SubspaceModelCreate extends StatelessWidget { final List subspaces; + final TextEditingController spaceNameController; + final List? spaceTagModels; + final List? allTags; + final List? products; + final SpaceTemplateModel? spaceModel; const SubspaceModelCreate( BuildContext context, { Key? key, required this.subspaces, + this.spaceTagModels, + required this.allTags, + required this.products, + required this.spaceModel, + required this.spaceNameController, }) : super(key: key); @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; - return Container( child: subspaces.isEmpty ? TextButton( @@ -29,11 +38,16 @@ class SubspaceModelCreate extends StatelessWidget { onPressed: () async { Navigator.of(context).pop(); - final result = await showDialog>( + await showDialog>( barrierDismissible: false, context: context, builder: (BuildContext context) { return CreateSubSpaceModelDialog( + allTags: allTags, + spaceName: spaceNameController.text, + spaceModel: spaceModel, + spaceTagModels: spaceTagModels, + products: products, isEdit: true, dialogTitle: subspaces.isEmpty ? 'Create Sub-space' @@ -42,14 +56,6 @@ class SubspaceModelCreate extends StatelessWidget { ); }, ); - - if (result != null) { - subspaces.clear(); - subspaces.addAll(result); - context - .read() - .add(AddSubspacesToSpaceTemplate(subspaces)); - } }, child: const ButtonContentWidget( icon: Icons.add, @@ -90,8 +96,8 @@ class SubspaceModelCreate extends StatelessWidget { ), GestureDetector( onTap: () async { - final result = - await showDialog>( + Navigator.of(context).pop(); + await showDialog>( barrierDismissible: false, context: context, builder: (BuildContext context) { @@ -99,17 +105,14 @@ class SubspaceModelCreate extends StatelessWidget { isEdit: true, dialogTitle: 'Edit Sub-space', existingSubSpaces: subspaces, + allTags: allTags, + spaceName: spaceNameController.text, + spaceTagModels: spaceTagModels, + products: products, + spaceModel: spaceModel, ); }, ); - - if (result != null) { - subspaces.clear(); - subspaces.addAll(result); - context - .read() - .add(AddSubspacesToSpaceTemplate(subspaces)); - } }, child: Chip( label: const Text( diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index f60e1cf3..f367012d 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -30,8 +30,7 @@ class TagChipDisplay extends StatelessWidget { @override Widget build(BuildContext context) { - return (spaceModel?.subspaceModels?.isNotEmpty == true || - spaceModel?.tags?.isNotEmpty == true || + return (spaceModel?.tags?.isNotEmpty == true || spaceModel?.subspaceModels ?.any((subspace) => subspace.tags?.isNotEmpty == true) == true) @@ -160,7 +159,6 @@ class TagChipDisplay extends StatelessWidget { List? tags, List? subspaces) { final Map productCounts = {}; - // Count products in spaceModel tags if (tags != null) { for (var tag in tags) { if (tag.product != null) { @@ -169,7 +167,6 @@ class TagChipDisplay extends StatelessWidget { } } - // Count products in subspaces if (subspaces != null) { for (var subspace in subspaces) { if (subspace.tags != null) { @@ -183,7 +180,6 @@ class TagChipDisplay extends StatelessWidget { } } - // Create SelectedProduct instances return productCounts.entries .map((entry) => SelectedProduct( productId: entry.key.uuid, diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index 74f11194..a8a6b1ac 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -4,8 +4,10 @@ import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart'; @@ -70,7 +72,21 @@ class AddDeviceTypeModelWidget extends StatelessWidget { children: [ CancelButton( label: 'Cancel', - onPressed: () => Navigator.of(context).pop(), + onPressed: () async { + Navigator.of(context).pop(); + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => CreateSpaceModelDialog( + products: products, + allTags: allTags, + spaceModel: SpaceTemplateModel( + modelName: spaceName, + subspaceModels: subspaces, + tags: spaceTagModels, + )), + ); + }, ), ActionButton( label: 'Continue', @@ -82,6 +98,14 @@ class AddDeviceTypeModelWidget extends StatelessWidget { Navigator.of(context).pop(); if (currentState.isNotEmpty) { + final initialTags = generateInitialTags( + spaceTagModels: spaceTagModels, + subspaces: subspaces, + ); + + final dialogTitle = initialTags.isNotEmpty + ? 'Edit Device' + : 'Assign Tags'; await showDialog( barrierDismissible: false, context: context, @@ -91,9 +115,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { addedProducts: currentState, allTags: allTags, spaceName: spaceName, - initialTags: generateInitialTags( - spaceTagModels: spaceTagModels, - subspaces: subspaces), + initialTags: initialTags, + title: dialogTitle, ), ); } From 9b3e4a59afa538c3459aef66f94feb445d3afd27 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 8 Jan 2025 20:07:53 +0400 Subject: [PATCH 063/175] fixed api call --- .../views/assign_tag_models_dialog.dart | 2 +- .../bloc/create_space_model_bloc.dart | 3 --- .../create_space_template_body_model.dart | 11 ++++++---- .../dialog/create_space_model_dialog.dart | 20 ++++++++----------- 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 182fbe55..2b2891cb 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -54,7 +54,7 @@ class AssignTagModelsDialog extends StatelessWidget { ); return AlertDialog( - title: const Text(title), + title: Text(title), backgroundColor: ColorsManager.whiteColors, content: SingleChildScrollView( child: Column( diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index d93f68c5..962d30da 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -17,7 +17,6 @@ class CreateSpaceModelBloc try { final spaceTemplate = event.spaceTemplate; - final tagBodyModels = spaceTemplate.tags?.map((tag) => tag.toTagBodyModel()).toList() ?? []; @@ -33,13 +32,11 @@ class CreateSpaceModelBloc ..tags = tagsubspaceBodyModels; }).toList() ?? []; - final spaceModelBody = CreateSpaceTemplateBodyModel( modelName: spaceTemplate.modelName, tags: tagBodyModels, subspaceModels: subspaceTemplateBodyModels); - print(spaceModelBody); final success = await _api.createSpaceModel(spaceModelBody); } catch (e) { print(e); diff --git a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart index 896fe000..e481a8b8 100644 --- a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart +++ b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - class TagBodyModel { late String uuid; late String tag; @@ -22,6 +20,13 @@ class TagBodyModel { class CreateSubspaceTemplateModel { late String subspaceName; late List? tags; + + Map toJson() { + return { + 'subspaceName': subspaceName, + 'tags': tags?.map((tag) => tag.toJson()).toList(), + }; + } } class CreateSpaceTemplateBodyModel { @@ -42,6 +47,4 @@ class CreateSpaceTemplateBodyModel { 'subspaceModels': subspaceModels, }; } - - } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 25a8fb63..b14dca52 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -13,8 +13,6 @@ import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace import 'package:syncrow_web/services/space_model_mang_api.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -import '../../models/subspace_template_model.dart'; - class CreateSpaceModelDialog extends StatelessWidget { final List? products; final List? allTags; @@ -46,7 +44,7 @@ class CreateSpaceModelDialog extends StatelessWidget { } else { bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( modelName: '', - subspaceModels: [], + subspaceModels: const [], ))); } @@ -103,15 +101,13 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ), const SizedBox(height: 16), - SubspaceModelCreate( - context, - subspaces: state.space.subspaceModels ?? [], - allTags: allTags, - products: products, - spaceModel: spaceModel, - spaceTagModels: spaceModel?.tags ?? [], - spaceNameController: spaceNameController - ), + SubspaceModelCreate(context, + subspaces: state.space.subspaceModels ?? [], + allTags: allTags, + products: products, + spaceModel: spaceModel, + spaceTagModels: spaceModel?.tags ?? [], + spaceNameController: spaceNameController), const SizedBox(height: 10), TagChipDisplay(context, screenWidth: screenWidth, From 17e025b69fe26432ad2a3f62712688e0452e7597 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 8 Jan 2025 21:04:03 +0400 Subject: [PATCH 064/175] added empty name validation --- .../bloc/create_space_model_bloc.dart | 38 +++++++++++++------ .../bloc/create_space_model_event.dart | 6 +++ .../bloc/create_space_model_state.dart | 8 +++- .../models/space_template_model.dart | 7 ++-- .../models/subspace_template_model.dart | 7 ++-- .../dialog/create_space_model_dialog.dart | 32 ++++++++++------ lib/utils/color_manager.dart | 5 ++- 7 files changed, 70 insertions(+), 33 deletions(-) diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 962d30da..8c4f81df 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -59,18 +59,6 @@ class CreateSpaceModelBloc emit(CreateSpaceModelLoaded(_space!)); }); - on((event, emit) { - final currentState = state; - - if (currentState is CreateSpaceModelLoaded) { - final updatedSpaceModel = - currentState.space.copyWith(modelName: event.name); - emit(CreateSpaceModelLoaded(updatedSpaceModel)); - } else { - emit(CreateSpaceModelError("Space template not initialized")); - } - }); - on((event, emit) { final currentState = state; @@ -86,5 +74,31 @@ class CreateSpaceModelBloc emit(CreateSpaceModelError("Space template not initialized")); } }); + + on((event, emit) { + final currentState = state; + print('Current State: $currentState'); + if (currentState is CreateSpaceModelLoaded) { + if (event.name.trim().isEmpty) { + print("set error message"); + + emit(CreateSpaceModelLoaded( + currentState.space, + errorMessage: "Model name cannot be empty", + )); + print('State emitted: CreateSpaceModelLoaded with updated model:'); + + } else { + final updatedSpaceModel = + currentState.space.copyWith(modelName: event.name); + print( + 'State emitted: CreateSpaceModelLoaded with updated model: $updatedSpaceModel'); + + emit(CreateSpaceModelLoaded(updatedSpaceModel)); + } + } else { + emit(CreateSpaceModelError("Space template not initialized")); + } + }); } } diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index 7944a8a8..e4288837 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -42,3 +42,9 @@ class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent { AddSubspacesToSpaceTemplate(this.subspaces); } + +class ValidateSpaceTemplateName extends CreateSpaceModelEvent { + final String name; + + ValidateSpaceTemplateName({required this.name}); +} \ No newline at end of file diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart index c05e8744..0fc5c48d 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart @@ -5,7 +5,7 @@ abstract class CreateSpaceModelState extends Equatable { const CreateSpaceModelState(); @override - List get props => []; + List get props => []; } class CreateSpaceModelInitial extends CreateSpaceModelState {} @@ -14,8 +14,12 @@ class CreateSpaceModelLoading extends CreateSpaceModelState {} class CreateSpaceModelLoaded extends CreateSpaceModelState { final SpaceTemplateModel space; + final String? errorMessage; - CreateSpaceModelLoaded(this.space); + CreateSpaceModelLoaded(this.space, {this.errorMessage}); + + @override + List get props => [space, errorMessage]; } class CreateSpaceModelError extends CreateSpaceModelState { 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 f62bbcf6..dea68dbc 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 @@ -34,9 +34,10 @@ class SpaceTemplateModel extends Equatable { ?.map((e) => SubspaceTemplateModel.fromJson(e)) .toList() ?? [], - tags: (json['tags'] as List) - .map((item) => TagModel.fromJson(item)) - .toList(), + tags: (json['tags'] as List?) + ?.map((item) => TagModel.fromJson(item)) + .toList() ?? + [], ); } diff --git a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart index 119728db..ac71c6b1 100644 --- a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart @@ -18,9 +18,10 @@ class SubspaceTemplateModel { uuid: json['uuid'] ?? '', subspaceName: json['subspaceName'] ?? '', disabled: json['disabled'] ?? false, - tags: (json['tags'] as List) - .map((item) => TagModel.fromJson(item)) - .toList(), + tags: (json['tags'] as List?) + ?.map((item) => TagModel.fromJson(item)) + .toList() ?? + [], ); } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index b14dca52..01e65039 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -61,6 +61,7 @@ class CreateSpaceModelDialog extends StatelessWidget { } else if (state is CreateSpaceModelLoaded) { final updatedSpaceModel = state.space; final subspaces = updatedSpaceModel.subspaceModels ?? []; + final isNameValid = spaceNameController.text.trim().isNotEmpty; return Column( mainAxisSize: MainAxisSize.min, @@ -87,6 +88,7 @@ class CreateSpaceModelDialog extends StatelessWidget { filled: true, fillColor: ColorsManager.textFieldGreyColor, hintText: 'Please enter the name', + errorText: state.errorMessage, hintStyle: const TextStyle( color: ColorsManager.lightGrayColor), border: OutlineInputBorder( @@ -130,19 +132,26 @@ class CreateSpaceModelDialog extends StatelessWidget { const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: () { - final updatedSpaceTemplate = - updatedSpaceModel.copyWith( - modelName: spaceNameController.text.trim(), - ); - context.read().add( - CreateSpaceTemplate( - spaceTemplate: updatedSpaceTemplate), - ); - }, + onPressed: state.errorMessage == null || + isNameValid + ? () { + final updatedSpaceTemplate = + updatedSpaceModel.copyWith( + modelName: + spaceNameController.text.trim(), + ); + context.read().add( + CreateSpaceTemplate( + spaceTemplate: + updatedSpaceTemplate), + ); + } + : null, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, - foregroundColor: ColorsManager.whiteColors, + foregroundColor: isNameValid + ? ColorsManager.whiteColors + : ColorsManager.whiteColorsWithOpacity, child: const Text('OK'), ), ), @@ -158,7 +167,6 @@ class CreateSpaceModelDialog extends StatelessWidget { ); } - // Default case (e.g., CreateSpaceModelInitial) return const Center(child: Text('Initializing...')); }, ), diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index eaf1e6b1..60aad8d5 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -5,8 +5,11 @@ abstract class ColorsManager { static const Color switchOffColor = Color(0x7F8D99AE); static const Color primaryColor = Color(0xFF0030CB); //023DFE static const Color secondaryTextColor = Color(0xFF848484); - static Color primaryColorWithOpacity = const Color(0xFF023DFE).withOpacity(0.6); + static Color primaryColorWithOpacity = + const Color(0xFF023DFE).withOpacity(0.6); static const Color whiteColors = Colors.white; + static Color whiteColorsWithOpacity = Colors.white.withOpacity(0.6); + static const Color secondaryColor = Color(0xFF023DFE); static const Color onSecondaryColor = Color(0xFF023DFE); static Color shadowBlackColor = Colors.black.withOpacity(0.2); From 48c064c71102aac528cf11b94f5535d1f35f4b90 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 8 Jan 2025 21:06:31 +0400 Subject: [PATCH 065/175] added create --- .../space_model/bloc/create_space_model_bloc.dart | 7 +++---- .../widgets/dialog/create_space_model_dialog.dart | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 8c4f81df..799d1e27 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -37,9 +37,9 @@ class CreateSpaceModelBloc tags: tagBodyModels, subspaceModels: subspaceTemplateBodyModels); - final success = await _api.createSpaceModel(spaceModelBody); + await _api.createSpaceModel(spaceModelBody); } catch (e) { - print(e); + emit(CreateSpaceModelError('Error creating space model')); } }); @@ -86,8 +86,7 @@ class CreateSpaceModelBloc currentState.space, errorMessage: "Model name cannot be empty", )); - print('State emitted: CreateSpaceModelLoaded with updated model:'); - + print('State emitted: CreateSpaceModelLoaded with updated model:'); } else { final updatedSpaceModel = currentState.space.copyWith(modelName: event.name); diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 01e65039..a3b9907d 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -145,6 +145,8 @@ class CreateSpaceModelDialog extends StatelessWidget { spaceTemplate: updatedSpaceTemplate), ); + + Navigator.of(context).pop(); } : null, backgroundColor: ColorsManager.secondaryColor, From 339a242e743551b188ca79948d6d47816f067d26 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 9 Jan 2025 00:07:51 +0400 Subject: [PATCH 066/175] added load new space models --- .../widgets/loaded_space_widget.dart | 17 ++- .../bloc/create_space_model_bloc.dart | 20 +-- .../bloc/create_space_model_event.dart | 4 +- .../space_model/bloc/space_model_bloc.dart | 36 ++++++ .../space_model/bloc/space_model_event.dart | 18 +++ .../space_model/bloc/space_model_state.dart | 29 +++++ .../models/space_template_model.dart | 10 +- .../space_model/view/space_model_page.dart | 122 +++++++++++------- .../dialog/create_space_model_dialog.dart | 23 +++- lib/services/api/http_service.dart | 21 ++- lib/services/space_model_mang_api.dart | 22 +++- lib/utils/constants/api_const.dart | 1 + 12 files changed, 249 insertions(+), 74 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart create mode 100644 lib/pages/spaces_management/space_model/bloc/space_model_event.dart create mode 100644 lib/pages/spaces_management/space_model/bloc/space_model_state.dart 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 5039340c..84ed32f7 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 @@ -1,12 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.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'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/gradient_canvas_border_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/view/space_model_page.dart'; +import 'package:syncrow_web/services/space_model_mang_api.dart'; class LoadedSpaceView extends StatefulWidget { final List communities; @@ -47,10 +50,16 @@ class _LoadedStateViewState extends State { ), hasSpaceModels ? Expanded( - child: SpaceModelPage( - spaceModels: widget.spaceModels ?? [], - products: widget.products, - )) + child: BlocProvider( + create: (context) => SpaceModelBloc( + api: SpaceModelManagementApi(), + initialSpaceModels: widget.spaceModels ?? [], + ), + child: SpaceModelPage( + products: widget.products, + ), + ), + ) : CommunityStructureArea( selectedCommunity: widget.selectedCommunity, selectedSpace: widget.selectedSpace, diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 799d1e27..8055e217 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -15,7 +15,7 @@ class CreateSpaceModelBloc CreateSpaceModelBloc(this._api) : super(CreateSpaceModelInitial()) { on((event, emit) async { try { - final spaceTemplate = event.spaceTemplate; + late SpaceTemplateModel spaceTemplate = event.spaceTemplate; final tagBodyModels = spaceTemplate.tags?.map((tag) => tag.toTagBodyModel()).toList() ?? @@ -37,7 +37,16 @@ class CreateSpaceModelBloc tags: tagBodyModels, subspaceModels: subspaceTemplateBodyModels); - await _api.createSpaceModel(spaceModelBody); + final newSpaceTemplate = await _api.createSpaceModel(spaceModelBody); + spaceTemplate.uuid = newSpaceTemplate?.uuid ?? ''; + + if (newSpaceTemplate != null) { + emit(CreateSpaceModelLoaded(spaceTemplate)); + + if (event.onCreate != null) { + event.onCreate!(spaceTemplate); + } + } } catch (e) { emit(CreateSpaceModelError('Error creating space model')); } @@ -77,22 +86,17 @@ class CreateSpaceModelBloc on((event, emit) { final currentState = state; - print('Current State: $currentState'); if (currentState is CreateSpaceModelLoaded) { if (event.name.trim().isEmpty) { - print("set error message"); emit(CreateSpaceModelLoaded( currentState.space, errorMessage: "Model name cannot be empty", )); - print('State emitted: CreateSpaceModelLoaded with updated model:'); } else { final updatedSpaceModel = currentState.space.copyWith(modelName: event.name); - print( - 'State emitted: CreateSpaceModelLoaded with updated model: $updatedSpaceModel'); - + emit(CreateSpaceModelLoaded(updatedSpaceModel)); } } else { diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index e4288837..9342c771 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -19,9 +19,11 @@ class UpdateSpaceTemplate extends CreateSpaceModelEvent { class CreateSpaceTemplate extends CreateSpaceModelEvent { final SpaceTemplateModel spaceTemplate; + final Function(SpaceTemplateModel)? onCreate; const CreateSpaceTemplate({ required this.spaceTemplate, + this.onCreate, }); @override @@ -47,4 +49,4 @@ class ValidateSpaceTemplateName extends CreateSpaceModelEvent { final String name; ValidateSpaceTemplateName({required this.name}); -} \ No newline at end of file +} diff --git a/lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart new file mode 100644 index 00000000..e383610d --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart @@ -0,0 +1,36 @@ +import 'package:flutter_bloc/flutter_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/bloc/space_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/services/space_model_mang_api.dart'; + +class SpaceModelBloc extends Bloc { + final SpaceModelManagementApi api; + + SpaceModelBloc({ + required this.api, + required List initialSpaceModels, + }) : super(SpaceModelLoaded(spaceModels: initialSpaceModels)) { + on(_onCreateSpaceModel); + } + + Future _onCreateSpaceModel( + CreateSpaceModel event, Emitter emit) async { + final currentState = state; + if (currentState is SpaceModelLoaded) { + try { + final newSpaceModel = + await api.getSpaceModel(event.newSpaceModel.uuid ?? ''); + + if (newSpaceModel != null) { + final updatedSpaceModels = + List.from(currentState.spaceModels) + ..add(newSpaceModel); + emit(SpaceModelLoaded(spaceModels: updatedSpaceModels)); + } + } catch (e) { + emit(SpaceModelError(message: e.toString())); + } + } + } +} diff --git a/lib/pages/spaces_management/space_model/bloc/space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/space_model_event.dart new file mode 100644 index 00000000..78331f3c --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/space_model_event.dart @@ -0,0 +1,18 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +abstract class SpaceModelEvent extends Equatable { + @override + List get props => []; +} + +class LoadSpaceModels extends SpaceModelEvent {} + +class CreateSpaceModel extends SpaceModelEvent { + final SpaceTemplateModel newSpaceModel; + + CreateSpaceModel({required this.newSpaceModel}); + + @override + List get props => [newSpaceModel]; +} diff --git a/lib/pages/spaces_management/space_model/bloc/space_model_state.dart b/lib/pages/spaces_management/space_model/bloc/space_model_state.dart new file mode 100644 index 00000000..53adf973 --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/space_model_state.dart @@ -0,0 +1,29 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +abstract class SpaceModelState extends Equatable { + @override + List get props => []; +} + +class SpaceModelInitial extends SpaceModelState {} + +class SpaceModelLoading extends SpaceModelState {} + +class SpaceModelLoaded extends SpaceModelState { + final List spaceModels; + + SpaceModelLoaded({required this.spaceModels}); + + @override + List get props => [spaceModels]; +} + +class SpaceModelError extends SpaceModelState { + final String message; + + SpaceModelError({required this.message}); + + @override + List get props => [message]; +} 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 dea68dbc..4f762c9a 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 @@ -6,7 +6,7 @@ import 'package:syncrow_web/utils/constants/action_enum.dart'; import 'package:uuid/uuid.dart'; class SpaceTemplateModel extends Equatable { - final String? uuid; + String? uuid; String modelName; List? subspaceModels; final List? tags; @@ -31,16 +31,18 @@ class SpaceTemplateModel extends Equatable { internalId: internalId, modelName: json['modelName'] ?? '', subspaceModels: (json['subspaceModels'] as List?) - ?.map((e) => SubspaceTemplateModel.fromJson(e)) + ?.where((e) => e is Map) // Validate type + .map((e) => + SubspaceTemplateModel.fromJson(e as Map)) .toList() ?? [], tags: (json['tags'] as List?) - ?.map((item) => TagModel.fromJson(item)) + ?.where((item) => item is Map) // Validate type + .map((item) => TagModel.fromJson(item as Map)) .toList() ?? [], ); } - SpaceTemplateModel copyWith({ String? uuid, String? modelName, diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 6fecd125..9838282c 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -1,61 +1,90 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.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/bloc/space_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/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class SpaceModelPage extends StatelessWidget { - final List spaceModels; final List? products; - const SpaceModelPage({Key? key, required this.spaceModels, this.products}) - : super(key: key); + const SpaceModelPage({Key? key, this.products}) : super(key: key); @override Widget build(BuildContext context) { - final allTagValues = getAllTagValues(); - return Scaffold( - backgroundColor: ColorsManager.whiteColors, - body: Padding( - padding: const EdgeInsets.fromLTRB(20.0, 16.0, 16.0, 16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: GridView.builder( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 10.0, - mainAxisSpacing: 10.0, - childAspectRatio: calculateChildAspectRatio(context), - ), - itemCount: spaceModels.length + 1, - itemBuilder: (context, index) { - if (index == spaceModels.length) { - return GestureDetector( - onTap: () { - showDialog( - context: context, - builder: (BuildContext context) { - return CreateSpaceModelDialog( - products: products, - allTags: allTagValues, - ); - }, - ); + return BlocBuilder( + builder: (context, state) { + if (state is SpaceModelLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is SpaceModelLoaded) { + final spaceModels = state.spaceModels; + final allTagValues = _getAllTagValues(spaceModels); + + return Scaffold( + backgroundColor: ColorsManager.whiteColors, + body: Padding( + padding: const EdgeInsets.fromLTRB(20.0, 16.0, 16.0, 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 10.0, + mainAxisSpacing: 10.0, + childAspectRatio: _calculateChildAspectRatio(context), + ), + itemCount: spaceModels.length + 1, + itemBuilder: (context, index) { + if (index == spaceModels.length) { + // Add Button + return GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return CreateSpaceModelDialog( + products: products, + allTags: allTagValues, + onLoad: (newModel) { + + context.read().add( + CreateSpaceModel( + newSpaceModel: newModel), + ); + }, + ); + }, + ); + }, + child: _buildAddContainer(), + ); + } + // Render existing space model + final model = spaceModels[index]; + return SpaceModelCardWidget(model: model); }, - child: _buildAddContainer(), - ); - } - final model = spaceModels[index]; - return SpaceModelCardWidget(model: model); - }, + ), + ), + ], ), ), - ], - ), - ), + ); + } else if (state is SpaceModelError) { + return Center( + child: Text( + 'Error: ${state.message}', + style: const TextStyle(color: Colors.red), + ), + ); + } + return const Center(child: Text('Initializing...')); + }, ); } @@ -101,23 +130,22 @@ class SpaceModelPage extends StatelessWidget { ); } - double calculateChildAspectRatio(BuildContext context) { + double _calculateChildAspectRatio(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; if (screenWidth > 1600) { return 3; } if (screenWidth > 1200) { - return 5; + return 5; } else if (screenWidth > 800) { return 5; } else { - return 6.0; + return 6.0; } } - List getAllTagValues() { + List _getAllTagValues(List spaceModels) { final List allTags = []; - for (final spaceModel in spaceModels) { if (spaceModel.tags != null) { allTags.addAll(spaceModel.listAllTagValues()); diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index a3b9907d..88a23bcf 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -17,10 +17,15 @@ class CreateSpaceModelDialog extends StatelessWidget { final List? products; final List? allTags; final SpaceTemplateModel? spaceModel; + final void Function(SpaceTemplateModel newModel)? onLoad; - const CreateSpaceModelDialog( - {Key? key, this.products, this.allTags, this.spaceModel}) - : super(key: key); + const CreateSpaceModelDialog({ + Key? key, + this.products, + this.allTags, + this.spaceModel, + this.onLoad, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -142,11 +147,15 @@ class CreateSpaceModelDialog extends StatelessWidget { ); context.read().add( CreateSpaceTemplate( - spaceTemplate: - updatedSpaceTemplate), + spaceTemplate: + updatedSpaceTemplate, + onCreate: (newModel) { + onLoad!(newModel); + Navigator.of(context) + .pop(); // Close the dialog + }, + ), ); - - Navigator.of(context).pop(); } : null, backgroundColor: ColorsManager.secondaryColor, diff --git a/lib/services/api/http_service.dart b/lib/services/api/http_service.dart index 2ed3da42..ee4584c9 100644 --- a/lib/services/api/http_service.dart +++ b/lib/services/api/http_service.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; import 'package:syncrow_web/services/api/http_interceptor.dart'; import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; @@ -34,13 +35,29 @@ class HTTPService { bool showServerMessage = true, }) async { try { + // Log the request path and query parameters + debugPrint('GET Request: $path'); + if (queryParameters != null) { + debugPrint('Query Parameters: $queryParameters'); + } + + // Perform the HTTP GET request final response = await client.get( path, queryParameters: queryParameters, ); - return expectedResponseModel(response.data); + + // Log the raw response data + debugPrint('Response Data: ${response.data}'); + + // Process the response using the expected model function + final result = expectedResponseModel(response.data); + + return result; } catch (error) { - rethrow; + // Log the error details + debugPrint('Error in GET Request: $error'); + rethrow; // Re-throw the error to propagate it further } } diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart index 82ed097f..19752aea 100644 --- a/lib/services/space_model_mang_api.dart +++ b/lib/services/space_model_mang_api.dart @@ -50,7 +50,27 @@ class SpaceModelManagementApi { ); return response; } catch (e) { - debugPrint('Error creating community: $e'); + debugPrint('Error creating space model: $e'); + return null; + } + } + + Future getSpaceModel(String spaceModelUuid) async { + try { + final response = await HTTPService().get( + path: ApiEndpoints.getSpaceModel + .replaceAll('{projectId}', TempConst.projectId) + .replaceAll('{spaceModelUuid}', spaceModelUuid), + showServerMessage: true, + expectedResponseModel: (json) { + debugPrint('Response JSON: $json'); + + return SpaceTemplateModel.fromJson(json['data']); + }, + ); + return response; + } catch (e) { + debugPrint('Error getting space model: $e'); return null; } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index ca4527fb..a2ff63dc 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -100,4 +100,5 @@ 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}'; } From d0b853b188536b95f12a6c204a2c48b0a7115108 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 9 Jan 2025 12:58:42 +0400 Subject: [PATCH 067/175] revert back http --- .../space_model/view/space_model_page.dart | 17 ++++--- lib/services/api/http_service.dart | 45 ++++--------------- 2 files changed, 19 insertions(+), 43 deletions(-) diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 9838282c..ea9a9cee 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -52,7 +52,6 @@ class SpaceModelPage extends StatelessWidget { products: products, allTags: allTagValues, onLoad: (newModel) { - context.read().add( CreateSpaceModel( newSpaceModel: newModel), @@ -67,7 +66,10 @@ class SpaceModelPage extends StatelessWidget { } // Render existing space model final model = spaceModels[index]; - return SpaceModelCardWidget(model: model); + return Container( + margin: const EdgeInsets.all(8.0), + child: SpaceModelCardWidget(model: model), + ); }, ), ), @@ -90,6 +92,7 @@ class SpaceModelPage extends StatelessWidget { Widget _buildAddContainer() { return Container( + margin: const EdgeInsets.all(8.0), decoration: BoxDecoration( color: ColorsManager.whiteColors, borderRadius: BorderRadius.circular(20), @@ -111,7 +114,7 @@ class SpaceModelPage extends StatelessWidget { child: Center( child: Container( width: 60, - height: 60, + height: 0, decoration: BoxDecoration( shape: BoxShape.circle, color: ColorsManager.neutralGray, @@ -133,14 +136,14 @@ class SpaceModelPage extends StatelessWidget { double _calculateChildAspectRatio(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; if (screenWidth > 1600) { - return 3; + return 2; // Taller cards for larger screens } if (screenWidth > 1200) { - return 5; + return 3; // Adjusted height for medium screens } else if (screenWidth > 800) { - return 5; + return 3.5; // Adjusted height for smaller screens } else { - return 6.0; + return 4.0; // Default ratio for smallest screens } } diff --git a/lib/services/api/http_service.dart b/lib/services/api/http_service.dart index ee4584c9..b75f05cf 100644 --- a/lib/services/api/http_service.dart +++ b/lib/services/api/http_service.dart @@ -1,7 +1,4 @@ -import 'dart:convert'; - import 'package:dio/dio.dart'; -import 'package:flutter/material.dart'; import 'package:syncrow_web/services/api/http_interceptor.dart'; import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; @@ -35,56 +32,32 @@ class HTTPService { bool showServerMessage = true, }) async { try { - // Log the request path and query parameters - debugPrint('GET Request: $path'); - if (queryParameters != null) { - debugPrint('Query Parameters: $queryParameters'); - } - - // Perform the HTTP GET request final response = await client.get( path, queryParameters: queryParameters, ); - - // Log the raw response data - debugPrint('Response Data: ${response.data}'); - - // Process the response using the expected model function - final result = expectedResponseModel(response.data); - - return result; + return expectedResponseModel(response.data); } catch (error) { - // Log the error details - debugPrint('Error in GET Request: $error'); - rethrow; // Re-throw the error to propagate it further + rethrow; } } - Future post({ - required String path, - Map? queryParameters, - Options? options, - dynamic body, - bool showServerMessage = true, - required T Function(dynamic) expectedResponseModel, - }) async { + Future post( + {required String path, + Map? queryParameters, + Options? options, + dynamic body, + bool showServerMessage = true, + required T Function(dynamic) expectedResponseModel}) async { try { - final bodyString = body is Map || body is List - ? jsonEncode(body) - : body?.toString() ?? 'null'; - - print("POST Request: $path, Body: $bodyString, Query: $queryParameters"); final response = await client.post( path, data: body, queryParameters: queryParameters, options: options, ); - print("POST Response: ${response.data.toString()}"); return expectedResponseModel(response.data); } catch (error) { - print("POST Error: $error"); rethrow; } } From 625b7b8304358902f22f2459a898bbdafadea2a5 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 9 Jan 2025 13:03:25 +0400 Subject: [PATCH 068/175] revert back --- .env.development | 2 +- .gitignore | 3 --- lib/utils/constants/temp_const.dart | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.env.development b/.env.development index 1fd358ec..e77609dc 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,2 @@ ENV_NAME=development -BASE_URL=http://localhost:4001 \ No newline at end of file +BASE_URL=https://syncrow-dev.azurewebsites.net \ No newline at end of file diff --git a/.gitignore b/.gitignore index ae866139..29a3a501 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,3 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release -lib/utils/constants/temp_const.dart -.env.development -lib/utils/constants/temp_const.dart diff --git a/lib/utils/constants/temp_const.dart b/lib/utils/constants/temp_const.dart index bcd1a1d6..e5847b98 100644 --- a/lib/utils/constants/temp_const.dart +++ b/lib/utils/constants/temp_const.dart @@ -1,3 +1,3 @@ class TempConst { - static const projectId = '0685c781-df33-4cbf-bf65-9f4e835eb468'; + static const projectId = '0e62577c-06fa-41b9-8a92-99a21fbaf51c'; } From 097e70b906d4b431f0f76d2ae804269b8210855a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 9 Jan 2025 13:32:09 +0400 Subject: [PATCH 069/175] color manager --- .../widgets/add_device_type_widget.dart | 2 +- .../community_structure_header_widget.dart | 2 +- .../widgets/dialogs/create_space_dialog.dart | 6 +-- .../widgets/dialogs/delete_dialogue.dart | 8 +-- .../dialogs/icon_selection_dialog.dart | 2 +- .../widgets/plus_button_widget.dart | 2 +- .../all_spaces/widgets/sidebar_widget.dart | 2 +- .../widgets/space_container_widget.dart | 2 +- .../all_spaces/widgets/space_widget.dart | 7 +-- .../views/assign_tag_models_dialog.dart | 8 +-- .../space_model/view/space_model_page.dart | 48 ++---------------- .../widgets/add_space_model_widget.dart | 50 +++++++++++++++++++ .../dialog/create_space_model_dialog.dart | 2 +- .../widgets/space_model_card_widget.dart | 4 +- .../widgets/subspace_chip_widget.dart | 2 +- .../widgets/subspace_model_create_widget.dart | 10 ++-- .../widgets/tag_chips_display_widget.dart | 10 ++-- 17 files changed, 88 insertions(+), 79 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/widgets/add_space_model_widget.dart diff --git a/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart index 93a38716..351eacce 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart @@ -99,7 +99,7 @@ class _AddDeviceWidgetState extends State { _buildActionButton('Cancel', ColorsManager.boxColor, ColorsManager.blackColor, () { Navigator.of(context).pop(); }), - _buildActionButton('Continue', ColorsManager.secondaryColor, Colors.white, () { + _buildActionButton('Continue', ColorsManager.secondaryColor, ColorsManager.whiteColors, () { Navigator.of(context).pop(); if (widget.onProductsSelected != null) { widget.onProductsSelected!(productCounts); diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart index 63306581..612f9101 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart @@ -178,7 +178,7 @@ class _CommunityStructureHeaderState extends State { padding: 2.0, height: buttonHeight, elevation: 0, - borderColor: Colors.grey.shade300, + borderColor: ColorsManager.lightGrayColor, child: Row( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index f6a02833..222881a8 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -104,7 +104,7 @@ class CreateSpaceDialogState extends State { width: screenWidth * 0.020, height: screenWidth * 0.020, decoration: const BoxDecoration( - color: Colors.white, + color: ColorsManager.whiteColors, shape: BoxShape.circle, ), child: SvgPicture.asset( @@ -143,7 +143,7 @@ class CreateSpaceDialogState extends State { } }); }, - style: const TextStyle(color: Colors.black), + style: const TextStyle(color: ColorsManager.blackColor), decoration: InputDecoration( hintText: 'Please enter the name', hintStyle: const TextStyle( @@ -212,7 +212,7 @@ class CreateSpaceDialogState extends State { ); }, backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, + foregroundColor: ColorsManager.blackColor, borderColor: ColorsManager.neutralGray, borderRadius: 16.0, padding: 10.0, // Reduced padding for smaller size diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/delete_dialogue.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/delete_dialogue.dart index 8607f9e0..27275be1 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/delete_dialogue.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/delete_dialogue.dart @@ -40,8 +40,8 @@ void showDeleteConfirmationDialog(BuildContext context, VoidCallback onConfirm, Navigator.of(context).pop(); // Close the first dialog showProcessingPopup(context, isSpace, onConfirm); }, - style: _dialogButtonStyle(Colors.blue), - child: const Text('Continue', style: TextStyle(color: Colors.white)), + style: _dialogButtonStyle(ColorsManager.spaceColor), + child: const Text('Continue', style: TextStyle(color: ColorsManager.whiteColors)), ), ], ), @@ -83,7 +83,7 @@ void showProcessingPopup(BuildContext context, bool isSpace, VoidCallback onDele ElevatedButton( onPressed: onDelete, style: _dialogButtonStyle(ColorsManager.warningRed), - child: const Text('Delete', style: TextStyle(color: Colors.white)), + child: const Text('Delete', style: TextStyle(color: ColorsManager.whiteColors)), ), CancelButton( label: 'Cancel', @@ -108,7 +108,7 @@ Widget _buildWarningIcon() { color: ColorsManager.warningRed, shape: BoxShape.circle, ), - child: const Icon(Icons.close, color: Colors.white, size: 40), + child: const Icon(Icons.close, color: ColorsManager.whiteColors, size: 40), ); } diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart index 5251ba32..b2a01988 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart @@ -28,7 +28,7 @@ class IconSelectionDialog extends StatelessWidget { borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.2), // Shadow color + color: ColorsManager.blackColor.withOpacity(0.2), // Shadow color blurRadius: 20, // Spread of the blur offset: const Offset(0, 8), // Offset of the shadow ), diff --git a/lib/pages/spaces_management/all_spaces/widgets/plus_button_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/plus_button_widget.dart index b077ac9d..40be7284 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/plus_button_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/plus_button_widget.dart @@ -45,7 +45,7 @@ class PlusButtonWidget extends StatelessWidget { color: ColorsManager.spaceColor, shape: BoxShape.circle, ), - child: const Icon(Icons.add, color: Colors.white, size: 20), + child: const Icon(Icons.add, color: ColorsManager.whiteColors, size: 20), ), ), ); 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 2d557e25..da67e6ed 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -120,7 +120,7 @@ class _SidebarWidgetState extends State { children: [ Text('Communities', style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: Colors.black, + color: ColorsManager.blackColor, )), GestureDetector( onTap: () => _navigateToBlank(context), diff --git a/lib/pages/spaces_management/all_spaces/widgets/space_container_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/space_container_widget.dart index 78af5f10..6f52eb50 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/space_container_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/space_container_widget.dart @@ -78,7 +78,7 @@ class SpaceContainerWidget extends StatelessWidget { borderRadius: BorderRadius.circular(15), boxShadow: [ BoxShadow( - color: Colors.grey.withOpacity(0.5), + color: ColorsManager.lightGrayColor.withOpacity(0.5), spreadRadius: 2, blurRadius: 5, offset: const Offset(0, 3), // Shadow position diff --git a/lib/pages/spaces_management/all_spaces/widgets/space_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/space_widget.dart index e4ce27cc..6e1f50c1 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/space_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/space_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; class SpaceWidget extends StatelessWidget { final String name; @@ -23,11 +24,11 @@ class SpaceWidget extends StatelessWidget { Container( padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( - color: Colors.white, + color: ColorsManager.whiteColors, borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( - color: Colors.grey.withOpacity(0.5), + color: ColorsManager.lightGrayColor.withOpacity(0.5), spreadRadius: 5, blurRadius: 7, offset: const Offset(0, 3), @@ -36,7 +37,7 @@ class SpaceWidget extends StatelessWidget { ), child: Row( children: [ - const Icon(Icons.location_on, color: Colors.blue), + const Icon(Icons.location_on, color: ColorsManager.spaceColor), const SizedBox(width: 8), Text(name, style: const TextStyle(fontSize: 16)), ], diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 2b2891cb..f4f2276f 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -96,7 +96,7 @@ class AssignTagModelsDialog extends StatelessWidget { 'No Data Available', style: TextStyle( fontSize: 14, - color: Colors.grey, + color: ColorsManager.lightGrayColor, ), ), ), @@ -159,7 +159,7 @@ class AssignTagModelsDialog extends StatelessWidget { ), style: const TextStyle( fontSize: 14, - color: Colors.black, + color: ColorsManager.blackColor, ), ), ), @@ -172,7 +172,7 @@ class AssignTagModelsDialog extends StatelessWidget { color: ColorsManager.whiteColors, icon: const Icon( Icons.arrow_drop_down, - color: Colors.black), + color: ColorsManager.blackColor), onSelected: (value) { controller.text = value; context @@ -273,7 +273,7 @@ class AssignTagModelsDialog extends StatelessWidget { if (state.errorMessage != null) Text( state.errorMessage!, - style: const TextStyle(color: Colors.red), + style: const TextStyle(color: ColorsManager.warningRed), ), ], ), diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index ea9a9cee..6b9818c9 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_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/add_space_model_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -61,7 +62,7 @@ class SpaceModelPage extends StatelessWidget { }, ); }, - child: _buildAddContainer(), + child: const AddSpaceModelWidget(), ); } // Render existing space model @@ -81,7 +82,7 @@ class SpaceModelPage extends StatelessWidget { return Center( child: Text( 'Error: ${state.message}', - style: const TextStyle(color: Colors.red), + style: const TextStyle(color: ColorsManager.warningRed), ), ); } @@ -90,49 +91,6 @@ class SpaceModelPage extends StatelessWidget { ); } - Widget _buildAddContainer() { - return Container( - margin: const EdgeInsets.all(8.0), - decoration: BoxDecoration( - color: ColorsManager.whiteColors, - borderRadius: BorderRadius.circular(20), - boxShadow: const [ - BoxShadow( - color: ColorsManager.semiTransparentBlackColor, - blurRadius: 15, - offset: Offset(0, 4), - spreadRadius: 0, - ), - BoxShadow( - color: ColorsManager.semiTransparentBlackColor, - blurRadius: 25, - offset: Offset(0, 15), - spreadRadius: -5, - ), - ], - ), - child: Center( - child: Container( - width: 60, - height: 0, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: ColorsManager.neutralGray, - border: Border.all( - color: ColorsManager.textFieldGreyColor, - width: 2.0, - ), - ), - child: const Icon( - Icons.add, - size: 40, - color: ColorsManager.spaceColor, - ), - ), - ), - ); - } - double _calculateChildAspectRatio(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; if (screenWidth > 1600) { diff --git a/lib/pages/spaces_management/space_model/widgets/add_space_model_widget.dart b/lib/pages/spaces_management/space_model/widgets/add_space_model_widget.dart new file mode 100644 index 00000000..20876a39 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/add_space_model_widget.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class AddSpaceModelWidget extends StatelessWidget { + const AddSpaceModelWidget({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(20), + boxShadow: const [ + BoxShadow( + color: ColorsManager.semiTransparentBlackColor, + blurRadius: 15, + offset: Offset(0, 4), + spreadRadius: 0, + ), + BoxShadow( + color: ColorsManager.semiTransparentBlackColor, + blurRadius: 25, + offset: Offset(0, 15), + spreadRadius: -5, + ), + ], + ), + child: Center( + child: Container( + width: 60, + height: 60, // Set a proper height here + decoration: BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.neutralGray, + border: Border.all( + color: ColorsManager.textFieldGreyColor, + width: 2.0, + ), + ), + child: const Icon( + Icons.add, + size: 40, + color: ColorsManager.spaceColor, + ), + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 88a23bcf..51222ad8 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -174,7 +174,7 @@ class CreateSpaceModelDialog extends StatelessWidget { } else if (state is CreateSpaceModelError) { return Text( 'Error: ${state.message}', - style: const TextStyle(color: Colors.red), + style: const TextStyle(color: ColorsManager.warningRed), ); } 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 208b0893..4471743b 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 @@ -33,11 +33,11 @@ class SpaceModelCardWidget extends StatelessWidget { return Container( decoration: BoxDecoration( - color: Colors.white, + color: ColorsManager.whiteColors, borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( - color: Colors.grey.withOpacity(0.5), + color: ColorsManager.lightGrayColor.withOpacity(0.5), spreadRadius: 2, blurRadius: 5, offset: const Offset(0, 3), diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart index debe3801..517448c8 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart @@ -25,7 +25,7 @@ class SubspaceChipWidget extends StatelessWidget { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: const BorderSide( - color: Colors.transparent, + color: ColorsManager.transparentColor, width: 0, ), ), diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 31ed8659..22444c3d 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -85,7 +85,7 @@ class SubspaceModelCreate extends StatelessWidget { style: const TextStyle( color: ColorsManager.spaceColor), // Text color ), - backgroundColor: Colors.white, // Chip background color + backgroundColor: ColorsManager.whiteColors, // Chip background color shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), // Rounded chip @@ -118,15 +118,15 @@ class SubspaceModelCreate extends StatelessWidget { label: const Text( 'Edit', style: TextStyle( - color: ColorsManager.spaceColor), // Text color + color: ColorsManager.spaceColor), ), backgroundColor: - Colors.white, // Background color for "Edit" + ColorsManager.whiteColors, shape: RoundedRectangleBorder( borderRadius: - BorderRadius.circular(16), // Rounded chip + BorderRadius.circular(16), side: const BorderSide( - color: ColorsManager.spaceColor), // Border color + color: ColorsManager.spaceColor), ), ), ), diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index f367012d..9cb356ef 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -71,7 +71,7 @@ class TagChipDisplay extends StatelessWidget { color: ColorsManager.spaceColor, ), ), - backgroundColor: Colors.white, + backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: const BorderSide( @@ -104,14 +104,14 @@ class TagChipDisplay extends StatelessWidget { label: const Text( 'Edit', style: TextStyle( - color: ColorsManager.spaceColor), // Text color + color: ColorsManager.spaceColor), ), backgroundColor: - Colors.white, // Background color for "Edit" + ColorsManager.whiteColors, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), // Rounded chip + borderRadius: BorderRadius.circular(16), side: const BorderSide( - color: ColorsManager.spaceColor), // Border color + color: ColorsManager.spaceColor), ), ), ), From 67516817ec8048ee324189e8988a5600cd541c4a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 9 Jan 2025 16:18:19 +0400 Subject: [PATCH 070/175] add asset validation --- .../widgets/dialogs/create_space_dialog.dart | 10 +++++--- lib/services/space_mana_api.dart | 25 +++++++++++-------- lib/utils/asset_validator.dart | 15 +++++++++++ 3 files changed, 36 insertions(+), 14 deletions(-) create mode 100644 lib/utils/asset_validator.dart diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 222881a8..054606cb 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/hoverable_button.dart'; +import 'package:syncrow_web/utils/asset_validator.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/space_icon_const.dart'; @@ -60,7 +61,7 @@ class CreateSpaceDialogState extends State { @override @override - Widget build(BuildContext context) { + Future build(BuildContext context) async { final screenWidth = MediaQuery.of(context).size.width; return AlertDialog( @@ -91,7 +92,9 @@ class CreateSpaceDialogState extends State { ), ), SvgPicture.asset( - selectedIcon, + await AssetValidator.isValidAsset(selectedIcon) + ? selectedIcon + : Assets.location, width: screenWidth * 0.04, height: screenWidth * 0.04, ), @@ -143,7 +146,8 @@ class CreateSpaceDialogState extends State { } }); }, - style: const TextStyle(color: ColorsManager.blackColor), + style: + const TextStyle(color: ColorsManager.blackColor), decoration: InputDecoration( hintText: 'Please enter the name', hintStyle: const TextStyle( diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index be789844..b637cbc7 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -19,23 +19,26 @@ class CommunitySpaceManagementApi { .replaceAll('{projectId}', TempConst.projectId), queryParameters: {'page': page}, expectedResponseModel: (json) { - List jsonData = json['data']; - hasNext = json['hasNext'] ?? false; - int currentPage = json['page'] ?? 1; - List communityList = jsonData.map((jsonItem) { - return CommunityModel.fromJson(jsonItem); - }).toList(); - - allCommunities.addAll(communityList); - page = currentPage + 1; - return communityList; + try { + List jsonData = json['data'] ?? []; + hasNext = json['hasNext'] ?? false; + int currentPage = json['page'] ?? 1; + List communityList = jsonData.map((jsonItem) { + return CommunityModel.fromJson(jsonItem); + }).toList(); + allCommunities.addAll(communityList); + page = currentPage + 1; + return communityList; + } catch (_) { + hasNext = false; + return []; + } }, ); } return allCommunities; } catch (e) { - debugPrint('Error fetching communities: $e'); return []; } } diff --git a/lib/utils/asset_validator.dart b/lib/utils/asset_validator.dart new file mode 100644 index 00000000..add6a3f4 --- /dev/null +++ b/lib/utils/asset_validator.dart @@ -0,0 +1,15 @@ +import 'package:flutter/services.dart'; + +class AssetValidator { + static Future isValidAsset(String? assetPath) async { + if (assetPath == null || assetPath.isEmpty) { + return false; + } + try { + await rootBundle.load(assetPath); + return true; + } catch (_) { + return false; + } + } +} From 1ab8c8341d0653614fbeb400a50e1b90d9de3900 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 9 Jan 2025 16:31:20 +0400 Subject: [PATCH 071/175] updated widget --- .../widgets/dialogs/create_space_dialog.dart | 112 +++-------------- .../selected_products_button_widget.dart | 118 ++++++++++++++++++ 2 files changed, 134 insertions(+), 96 deletions(-) create mode 100644 lib/pages/spaces_management/all_spaces/widgets/selected_products_button_widget.dart diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 054606cb..1ad8f52c 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -8,7 +8,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/hoverable_button.dart'; -import 'package:syncrow_web/utils/asset_validator.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/selected_products_button_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/space_icon_const.dart'; @@ -60,8 +60,7 @@ class CreateSpaceDialogState extends State { } @override - @override - Future build(BuildContext context) async { + Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; return AlertDialog( @@ -92,9 +91,7 @@ class CreateSpaceDialogState extends State { ), ), SvgPicture.asset( - await AssetValidator.isValidAsset(selectedIcon) - ? selectedIcon - : Assets.location, + selectedIcon, width: screenWidth * 0.04, height: screenWidth * 0.04, ), @@ -107,7 +104,7 @@ class CreateSpaceDialogState extends State { width: screenWidth * 0.020, height: screenWidth * 0.020, decoration: const BoxDecoration( - color: ColorsManager.whiteColors, + color: Colors.white, shape: BoxShape.circle, ), child: SvgPicture.asset( @@ -146,8 +143,7 @@ class CreateSpaceDialogState extends State { } }); }, - style: - const TextStyle(color: ColorsManager.blackColor), + style: const TextStyle(color: Colors.black), decoration: InputDecoration( hintText: 'Please enter the name', hintStyle: const TextStyle( @@ -199,7 +195,16 @@ class CreateSpaceDialogState extends State { ), const SizedBox(height: 16), if (selectedProducts.isNotEmpty) - _buildSelectedProductsButtons(widget.products ?? []) + SelectedProductsButtons( + products: widget.products ?? [], + selectedProducts: selectedProducts, + onProductsUpdated: (updatedProducts) { + setState(() { + selectedProducts = updatedProducts; + }); + }, + context: context, + ) else DefaultButton( onPressed: () { @@ -216,7 +221,7 @@ class CreateSpaceDialogState extends State { ); }, backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: ColorsManager.blackColor, + foregroundColor: Colors.black, borderColor: ColorsManager.neutralGray, borderRadius: 16.0, padding: 10.0, // Reduced padding for smaller size @@ -317,74 +322,6 @@ class CreateSpaceDialogState extends State { ); } - Widget _buildSelectedProductsButtons(List products) { - final screenWidth = MediaQuery.of(context).size.width; - - return Container( - width: screenWidth * 0.6, - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: ColorsManager.neutralGray, - width: 2, - ), - ), - child: Wrap( - spacing: 8, - runSpacing: 8, - children: [ - for (var i = 0; i < selectedProducts.length; i++) ...[ - HoverableButton( - iconPath: - _mapIconToProduct(selectedProducts[i].productId, products), - text: 'x${selectedProducts[i].count}', - onTap: () { - setState(() { - selectedProducts.remove(selectedProducts[i]); - }); - // Handle button tap - }, - ), - if (i < selectedProducts.length - 1) - const SizedBox( - width: 2), // Add space except after the last button - ], - const SizedBox(width: 2), - GestureDetector( - onTap: () { - showDialog( - context: context, - builder: (context) => AddDeviceWidget( - products: widget.products, - initialSelectedProducts: selectedProducts, - onProductsSelected: (selectedProductsMap) { - setState(() { - selectedProducts = selectedProductsMap; - }); - }, - ), - ); - }, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - color: ColorsManager.whiteColors, - borderRadius: BorderRadius.circular(16), - ), - child: const Icon( - Icons.add, - color: ColorsManager.spaceColor, - size: 24, - ), - ), - ), - ], - ), - ); - } - bool _isNameConflict(String value) { return (widget.parentSpace?.children.any((child) => child.name == value) ?? false) || @@ -393,21 +330,4 @@ class CreateSpaceDialogState extends State { (widget.editSpace?.children.any((child) => child.name == value) ?? false); } - - String _mapIconToProduct(String uuid, List products) { - // Find the product with the matching UUID - final product = products.firstWhere( - (product) => product.uuid == uuid, - orElse: () => ProductModel( - uuid: '', - catName: '', - prodId: '', - prodType: '', - name: '', - icon: Assets.presenceSensor, - ), - ); - - return product.icon ?? Assets.presenceSensor; - } } diff --git a/lib/pages/spaces_management/all_spaces/widgets/selected_products_button_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/selected_products_button_widget.dart new file mode 100644 index 00000000..7076a580 --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/widgets/selected_products_button_widget.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/hoverable_button.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class SelectedProductsButtons extends StatelessWidget { + final List products; + final List selectedProducts; + final Function(List) onProductsUpdated; + final BuildContext context; + + const SelectedProductsButtons({ + Key? key, + required this.products, + required this.selectedProducts, + required this.onProductsUpdated, + required this.context, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + return Container( + width: screenWidth * 0.6, + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: ColorsManager.neutralGray, + width: 2, + ), + ), + child: Wrap( + spacing: 8, + runSpacing: 8, + children: [ + ..._buildSelectedProductButtons(), + _buildAddButton(), + ], + ), + ); + } + + List _buildSelectedProductButtons() { + return [ + for (var i = 0; i < selectedProducts.length; i++) ...[ + HoverableButton( + iconPath: _mapIconToProduct(selectedProducts[i].productId, products), + text: 'x${selectedProducts[i].count}', + onTap: () { + _removeProduct(i); + }, + ), + if (i < selectedProducts.length - 1) + const SizedBox(width: 2), // Add space except after the last button + ], + ]; + } + + Widget _buildAddButton() { + return GestureDetector( + onTap: _showAddDeviceDialog, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(16), + ), + child: const Icon( + Icons.add, + color: ColorsManager.spaceColor, + size: 24, + ), + ), + ); + } + + void _showAddDeviceDialog() { + showDialog( + context: context, + builder: (context) => AddDeviceWidget( + products: products, + initialSelectedProducts: selectedProducts, + onProductsSelected: (selectedProductsMap) { + onProductsUpdated(selectedProductsMap); + }, + ), + ); + } + + void _removeProduct(int index) { + final updatedProducts = [...selectedProducts]; + updatedProducts.removeAt(index); + onProductsUpdated(updatedProducts); + } + + String _mapIconToProduct(String uuid, List products) { + // Find the product with the matching UUID + final product = products.firstWhere( + (product) => product.uuid == uuid, + orElse: () => ProductModel( + uuid: '', + catName: '', + prodId: '', + prodType: '', + name: '', + icon: Assets.presenceSensor, + ), + ); + + return product.icon ?? Assets.presenceSensor; + } +} From a7e75548133ff007a8e79f1cc7270a1fe71c78c6 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 9 Jan 2025 16:39:46 +0400 Subject: [PATCH 072/175] added buttons --- .../widgets/dialogs/create_space_dialog.dart | 71 +++++++++++++++++++ lib/utils/constants/assets.dart | 1 + 2 files changed, 72 insertions(+) diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 1ad8f52c..25c3d5b3 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -194,6 +194,77 @@ class CreateSpaceDialogState extends State { ), ), const SizedBox(height: 16), + DefaultButton( + onPressed: () {}, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: Colors.black, + borderColor: ColorsManager.neutralGray, + borderRadius: 16.0, + padding: 10.0, // Reduced padding for smaller size + child: Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + Assets.addIcon, + width: screenWidth * + 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), + ), + const SizedBox(width: 3), + Flexible( + child: Text( + 'Link a space model', + overflow: TextOverflow + .ellipsis, // Prevent overflow + style: Theme.of(context) + .textTheme + .bodyMedium, + ), + ), + ], + ), + )), + const SizedBox(height: 30), + DefaultButton( + onPressed: () {}, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: Colors.black, + borderColor: ColorsManager.neutralGray, + borderRadius: 16.0, + padding: 10.0, // Reduced padding for smaller size + child: Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + Assets.addIcon, + width: screenWidth * + 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), + ), + const SizedBox(width: 3), + Flexible( + child: Text( + 'Create sub space', + overflow: TextOverflow + .ellipsis, // Prevent overflow + style: Theme.of(context) + .textTheme + .bodyMedium, + ), + ), + ], + ), + )), if (selectedProducts.isNotEmpty) SelectedProductsButtons( products: widget.products ?? [], diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 1f4074c4..a9deb3c7 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -397,5 +397,6 @@ class Assets { static const String filterTableIcon = 'assets/icons/filter_table_icon.svg'; static const String ZtoAIcon = 'assets/icons/ztoa_icon.png'; static const String AtoZIcon = 'assets/icons/atoz_icon.png'; + static const String link = 'assets/icons/link.svg'; } //user_management.svg From e70df16de35b781436104cfe6f82bc81e33b1d32 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 9 Jan 2025 16:39:54 +0400 Subject: [PATCH 073/175] added asset --- assets/icons/link.svg | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 assets/icons/link.svg diff --git a/assets/icons/link.svg b/assets/icons/link.svg new file mode 100644 index 00000000..9cd75905 --- /dev/null +++ b/assets/icons/link.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + From 8aa493a15ec65befad17948b6c6180bf3d42b8ee Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sat, 11 Jan 2025 15:42:35 +0400 Subject: [PATCH 074/175] added space model link to space dialog --- .../bloc/space_management_bloc.dart | 20 +- .../bloc/space_management_state.dart | 8 +- .../all_spaces/model/space_model.dart | 4 +- .../view/spaces_management_page.dart | 8 +- .../widgets/community_structure_widget.dart | 7 + .../widgets/dialogs/create_space_dialog.dart | 538 ++++++++++-------- .../widgets/loaded_space_widget.dart | 35 +- .../bloc/link_space_model_bloc.dart | 12 + .../bloc/link_space_model_event.dart | 7 + .../bloc/link_space_model_state.dart | 9 + .../view/link_space_model_dialog.dart | 131 +++++ .../space_model/view/space_model_page.dart | 8 +- 12 files changed, 502 insertions(+), 285 deletions(-) create mode 100644 lib/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart create mode 100644 lib/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart create mode 100644 lib/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart create mode 100644 lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 2b749a32..f8ed1b86 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -172,8 +172,12 @@ class SpaceManagementBloc }).toList(), ); + List spaceModels = + await _spaceModelApi.listSpaceModels(page: 1); emit(SpaceManagementLoaded( - communities: updatedCommunities, products: _cachedProducts ?? [])); + communities: updatedCommunities, + products: _cachedProducts ?? [], + spaceModels: spaceModels)); } catch (e) { emit(SpaceManagementError('Error loading communities and spaces: $e')); } @@ -276,12 +280,16 @@ class SpaceManagementBloc final communities = List.from( (previousState as dynamic).communities, ); + + final spaceModels = List.from( + (previousState as dynamic).spaceModels, + ); emit(SpaceManagementLoaded( - communities: communities, - products: _cachedProducts ?? [], - selectedCommunity: selectedCommunity, - selectedSpace: selectedSpace, - )); + communities: communities, + products: _cachedProducts ?? [], + selectedCommunity: selectedCommunity, + selectedSpace: selectedSpace, + spaceModels: spaceModels ?? [])); } } catch (e) { emit(SpaceManagementError('Error updating state: $e')); diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart index 3ceafba9..635c244d 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart @@ -20,12 +20,15 @@ class SpaceManagementLoaded extends SpaceManagementState { final List products; CommunityModel? selectedCommunity; SpaceModel? selectedSpace; + List? spaceModels; SpaceManagementLoaded( {required this.communities, required this.products, this.selectedCommunity, - this.selectedSpace}); + this.selectedSpace, + this.spaceModels + }); } class SpaceModelManagenetLoaded extends SpaceManagementState { @@ -35,10 +38,13 @@ class SpaceModelManagenetLoaded extends SpaceManagementState { class BlankState extends SpaceManagementState { final List communities; final List products; + List? spaceModels; + BlankState({ required this.communities, required this.products, + this.spaceModels }); } diff --git a/lib/pages/spaces_management/all_spaces/model/space_model.dart b/lib/pages/spaces_management/all_spaces/model/space_model.dart index 4bdf4ece..ba5d83d6 100644 --- a/lib/pages/spaces_management/all_spaces/model/space_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/space_model.dart @@ -1,7 +1,7 @@ import 'dart:ui'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:uuid/uuid.dart'; @@ -21,6 +21,7 @@ class SpaceModel { bool isHovered; SpaceStatus status; String internalId; + SpaceTemplateModel? spaceModel; List outgoingConnections = []; // Connections from this space Connection? incomingConnection; // Connections to this space @@ -40,6 +41,7 @@ class SpaceModel { this.isHovered = false, this.incomingConnection, this.status = SpaceStatus.unchanged, + this.spaceModel, }) : internalId = internalId ?? const Uuid().v4(); factory SpaceModel.fromJson(Map json, diff --git a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart index 77f878b7..24b1b5cb 100644 --- a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart +++ b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart @@ -51,19 +51,25 @@ class SpaceManagementPageState extends State { selectedCommunity: null, selectedSpace: null, products: state.products, + shouldNavigateToSpaceModelPage: false, ); } else if (state is SpaceManagementLoaded) { + print("sdksndsnadf ${state.spaceModels}"); return LoadedSpaceView( communities: state.communities, selectedCommunity: state.selectedCommunity, selectedSpace: state.selectedSpace, products: state.products, + spaceModels: state.spaceModels, + shouldNavigateToSpaceModelPage: false, ); } else if (state is SpaceModelLoaded) { return LoadedSpaceView( communities: state.communities, products: state.products, - spaceModels: state.spaceModels); + spaceModels: state.spaceModels, + shouldNavigateToSpaceModelPage: true, + ); } else if (state is SpaceManagementError) { return Center(child: Text('Error: ${state.errorMessage}')); } diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index a929c6fd..9225f5a9 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -17,6 +17,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/c import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/curved_line_painter.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_card_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_container_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CommunityStructureArea extends StatefulWidget { @@ -26,6 +27,8 @@ class CommunityStructureArea extends StatefulWidget { final ValueChanged? onSpaceSelected; final List communities; final List spaces; + final List? spaceModels; + CommunityStructureArea({ this.selectedCommunity, @@ -34,6 +37,7 @@ class CommunityStructureArea extends StatefulWidget { this.products, required this.spaces, this.onSpaceSelected, + this.spaceModels, }); @override @@ -49,9 +53,11 @@ class _CommunityStructureAreaState extends State { bool isEditingName = false; late TransformationController _transformationController; + @override void initState() { super.initState(); + print("sxdsf ${widget.spaceModels}"); spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; @@ -284,6 +290,7 @@ class _CommunityStructureAreaState extends State { builder: (BuildContext context) { return CreateSpaceDialog( products: widget.products, + spaceModels: widget.spaceModels, parentSpace: parentIndex != null ? spaces[parentIndex] : null, onCreateSpace: (String name, String icon, List selectedProducts) { diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 25c3d5b3..f3d11d91 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -7,8 +7,8 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_pr import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/hoverable_button.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/selected_products_button_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/space_icon_const.dart'; @@ -23,6 +23,7 @@ class CreateSpaceDialog extends StatefulWidget { final List selectedProducts; final SpaceModel? parentSpace; final SpaceModel? editSpace; + final List? spaceModels; const CreateSpaceDialog( {super.key, @@ -33,7 +34,8 @@ class CreateSpaceDialog extends StatefulWidget { this.icon, this.isEdit = false, this.editSpace, - this.selectedProducts = const []}); + this.selectedProducts = const [], + this.spaceModels}); @override CreateSpaceDialogState createState() => CreateSpaceDialogState(); @@ -59,275 +61,289 @@ class CreateSpaceDialogState extends State { enteredName.isNotEmpty || nameController.text.isNotEmpty; } + @override @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; - return AlertDialog( title: widget.isEdit ? const Text('Edit Space') : const Text('Create New Space'), backgroundColor: ColorsManager.whiteColors, content: SizedBox( - width: screenWidth * 0.5, // Limit dialog width + width: screenWidth * 0.5, child: SingleChildScrollView( - // Scrollable content to prevent overflow - child: Column( - mainAxisSize: MainAxisSize.min, + child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Stack( - alignment: Alignment.center, - children: [ - Container( - width: screenWidth * 0.1, // Adjusted width - height: screenWidth * 0.1, // Adjusted height - decoration: const BoxDecoration( - color: ColorsManager.boxColor, - shape: BoxShape.circle, - ), - ), - SvgPicture.asset( - selectedIcon, - width: screenWidth * 0.04, - height: screenWidth * 0.04, - ), - Positioned( - top: 6, - right: 6, - child: InkWell( - onTap: _showIconSelectionDialog, - child: Container( - width: screenWidth * 0.020, - height: screenWidth * 0.020, - decoration: const BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, - ), - child: SvgPicture.asset( - Assets.iconEdit, - width: screenWidth * 0.06, - height: screenWidth * 0.06, - ), - ), - ), - ), - ], - ), - const SizedBox(width: 16), - Expanded( - // Ensure the text field expands responsively - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + Expanded( + flex: 1, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + // crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Stack( + alignment: Alignment.center, children: [ - TextField( - controller: nameController, - onChanged: (value) { - enteredName = value.trim(); - setState(() { - isNameFieldExist = false; - isOkButtonEnabled = false; - isNameFieldInvalid = value.isEmpty; - - if (!isNameFieldInvalid) { - if (_isNameConflict(value)) { - isNameFieldExist = true; - isOkButtonEnabled = false; - } else { - isNameFieldExist = false; - isOkButtonEnabled = true; - } - } - }); - }, - style: const TextStyle(color: Colors.black), - decoration: InputDecoration( - hintText: 'Please enter the name', - hintStyle: const TextStyle( - fontSize: 14, - color: ColorsManager.lightGrayColor, - fontWeight: FontWeight.w400, - ), - filled: true, - fillColor: ColorsManager.boxColor, - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: BorderSide( - color: isNameFieldInvalid || isNameFieldExist - ? ColorsManager.red - : ColorsManager.boxColor, - width: 1.5, + Container( + width: screenWidth * 0.1, + height: screenWidth * 0.1, + decoration: const BoxDecoration( + color: ColorsManager.boxColor, + shape: BoxShape.circle, + ), + ), + SvgPicture.asset( + selectedIcon, + width: screenWidth * 0.04, + height: screenWidth * 0.04, + ), + Positioned( + top: 20, + right: 20, + child: InkWell( + onTap: _showIconSelectionDialog, + child: Container( + width: 24, + height: 24, + decoration: const BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: const BorderSide( - color: ColorsManager.boxColor, - width: 1.5, + child: SvgPicture.asset( + Assets.iconEdit, + width: 16, + height: 16, ), ), ), ), - if (isNameFieldInvalid) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - '*Space name should not be empty.', - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: ColorsManager.red), - ), - ), - if (isNameFieldExist) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - '*Name already exist', - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: ColorsManager.red), - ), - ), - const SizedBox(height: 16), - DefaultButton( - onPressed: () {}, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.addIcon, - width: screenWidth * - 0.015, // Adjust icon size - height: screenWidth * 0.015, - ), - ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Link a space model', - overflow: TextOverflow - .ellipsis, // Prevent overflow - style: Theme.of(context) - .textTheme - .bodyMedium, - ), - ), - ], - ), - )), - const SizedBox(height: 30), - DefaultButton( - onPressed: () {}, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.addIcon, - width: screenWidth * - 0.015, // Adjust icon size - height: screenWidth * 0.015, - ), - ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Create sub space', - overflow: TextOverflow - .ellipsis, // Prevent overflow - style: Theme.of(context) - .textTheme - .bodyMedium, - ), - ), - ], - ), - )), - if (selectedProducts.isNotEmpty) - SelectedProductsButtons( - products: widget.products ?? [], - selectedProducts: selectedProducts, - onProductsUpdated: (updatedProducts) { - setState(() { - selectedProducts = updatedProducts; - }); - }, - context: context, - ) - else - DefaultButton( - onPressed: () { - showDialog( - context: context, - builder: (context) => AddDeviceWidget( - products: widget.products, - onProductsSelected: (selectedProductsMap) { - setState(() { - selectedProducts = selectedProductsMap; - }); - }, - ), - ); - }, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.addIcon, - width: screenWidth * - 0.015, // Adjust icon size - height: screenWidth * 0.015, - ), - ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Add devices / Assign a space model', - overflow: TextOverflow - .ellipsis, // Prevent overflow - style: Theme.of(context) - .textTheme - .bodyMedium, - ), - ), - ], - ), - )), ], ), - ), - ], + ], + ), + ), + const SizedBox(width: 20), + Expanded( + flex: 2, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextField( + controller: nameController, + onChanged: (value) { + enteredName = value.trim(); + setState(() { + isNameFieldExist = false; + isOkButtonEnabled = false; + isNameFieldInvalid = value.isEmpty; + + if (!isNameFieldInvalid) { + if (_isNameConflict(value)) { + isNameFieldExist = true; + isOkButtonEnabled = false; + } else { + isNameFieldExist = false; + isOkButtonEnabled = true; + } + } + }); + }, + style: const TextStyle(color: Colors.black), + decoration: InputDecoration( + hintText: 'Please enter the name', + hintStyle: const TextStyle( + fontSize: 14, + color: ColorsManager.lightGrayColor, + fontWeight: FontWeight.w400, + ), + filled: true, + fillColor: ColorsManager.boxColor, + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: isNameFieldInvalid || isNameFieldExist + ? ColorsManager.red + : ColorsManager.boxColor, + width: 1.5, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: const BorderSide( + color: ColorsManager.boxColor, + width: 1.5, + ), + ), + ), + ), + if (isNameFieldInvalid) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + '*Space name should not be empty.', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.red), + ), + ), + if (isNameFieldExist) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + '*Name already exist', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.red), + ), + ), + const SizedBox(height: 10), + DefaultButton( + onPressed: () { + _showLinkSpaceModelDialog(context); + }, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: Colors.black, + borderColor: ColorsManager.neutralGray, + borderRadius: 16.0, + padding: 10.0, // Reduced padding for smaller size + child: Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + Assets.link, + width: screenWidth * 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), + ), + const SizedBox(width: 3), + Flexible( + child: Text( + 'Link a space model', + overflow: + TextOverflow.ellipsis, // Prevent overflow + style: Theme.of(context).textTheme.bodyMedium, + ), + ), + ], + ), + ), + ), + const SizedBox(height: 25), + const Row( + children: [ + Expanded( + child: Divider( + color: ColorsManager.neutralGray, + thickness: 1.0, + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 8.0), + child: Text( + 'OR', + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + ), + Expanded( + child: Divider( + color: ColorsManager.neutralGray, + thickness: 1.0, + ), + ), + ], + ), + const SizedBox(height: 25), + DefaultButton( + onPressed: () {}, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: Colors.black, + borderColor: ColorsManager.neutralGray, + borderRadius: 16.0, + padding: 10.0, // Reduced padding for smaller size + child: Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + Assets.addIcon, + width: screenWidth * 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), + ), + const SizedBox(width: 3), + Flexible( + child: Text( + 'Create sub space', + overflow: + TextOverflow.ellipsis, // Prevent overflow + style: Theme.of(context).textTheme.bodyMedium, + ), + ), + ], + ), + ), + ), + const SizedBox(height: 10), + DefaultButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => AddDeviceWidget( + products: widget.products, + onProductsSelected: (selectedProductsMap) { + setState(() { + selectedProducts = selectedProductsMap; + }); + }, + ), + ); + }, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: Colors.black, + borderColor: ColorsManager.neutralGray, + borderRadius: 16.0, + padding: 10.0, // Reduced padding for smaller size + child: Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + Assets.addIcon, + width: screenWidth * 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), + ), + const SizedBox(width: 3), + Flexible( + child: Text( + 'Add devices', + overflow: + TextOverflow.ellipsis, // Prevent overflow + style: Theme.of(context).textTheme.bodyMedium, + ), + ), + ], + ), + ), + ), + ], + ), ), ], ), @@ -401,4 +417,22 @@ class CreateSpaceDialogState extends State { (widget.editSpace?.children.any((child) => child.name == value) ?? false); } + + void _showLinkSpaceModelDialog(BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) { + return LinkSpaceModelDialog( + spaceModels: widget.spaceModels ?? [], + onSave: (selectedModel) { + if (selectedModel != null) { + print('Selected Model: ${selectedModel.modelName}'); + } else { + print('No model selected'); + } + }, + ); + }, + ); + } } 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 84ed32f7..cdba0a5a 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 @@ -11,12 +11,13 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/space_tem import 'package:syncrow_web/pages/spaces_management/space_model/view/space_model_page.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; -class LoadedSpaceView extends StatefulWidget { +class LoadedSpaceView extends StatelessWidget { final List communities; final CommunityModel? selectedCommunity; final SpaceModel? selectedSpace; final List? products; final List? spaceModels; + final bool shouldNavigateToSpaceModelPage; const LoadedSpaceView({ super.key, @@ -25,17 +26,11 @@ class LoadedSpaceView extends StatefulWidget { this.selectedSpace, this.products, this.spaceModels, + required this.shouldNavigateToSpaceModelPage }); - @override - _LoadedStateViewState createState() => _LoadedStateViewState(); -} - -class _LoadedStateViewState extends State { @override Widget build(BuildContext context) { - final bool hasSpaceModels = - widget.spaceModels != null && widget.spaceModels!.isNotEmpty; return Stack( clipBehavior: Clip.none, @@ -43,29 +38,29 @@ class _LoadedStateViewState extends State { Row( children: [ SidebarWidget( - communities: widget.communities, - selectedSpaceUuid: widget.selectedSpace?.uuid ?? - widget.selectedCommunity?.uuid ?? - '', + communities: communities, + selectedSpaceUuid: + selectedSpace?.uuid ?? selectedCommunity?.uuid ?? '', ), - hasSpaceModels + shouldNavigateToSpaceModelPage ? Expanded( child: BlocProvider( create: (context) => SpaceModelBloc( api: SpaceModelManagementApi(), - initialSpaceModels: widget.spaceModels ?? [], + initialSpaceModels: spaceModels ?? [], ), child: SpaceModelPage( - products: widget.products, + products: products, ), ), ) : CommunityStructureArea( - selectedCommunity: widget.selectedCommunity, - selectedSpace: widget.selectedSpace, - spaces: widget.selectedCommunity?.spaces ?? [], - products: widget.products, - communities: widget.communities, + selectedCommunity: selectedCommunity, + selectedSpace: selectedSpace, + spaces: selectedCommunity?.spaces ?? [], + products: products, + communities: communities, + spaceModels: spaceModels, ), ], ), 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 new file mode 100644 index 00000000..aa9a446d --- /dev/null +++ b/lib/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart @@ -0,0 +1,12 @@ +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 new file mode 100644 index 00000000..8bff0202 --- /dev/null +++ b/lib/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart @@ -0,0 +1,7 @@ +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 new file mode 100644 index 00000000..cc745e4d --- /dev/null +++ b/lib/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart @@ -0,0 +1,9 @@ +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/view/link_space_model_dialog.dart b/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart new file mode 100644 index 00000000..44cbbc67 --- /dev/null +++ b/lib/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart @@ -0,0 +1,131 @@ +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/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'; + +class LinkSpaceModelDialog extends StatelessWidget { + final Function(SpaceTemplateModel?)? onSave; + final int? initialSelectedIndex; + final List spaceModels; + + const LinkSpaceModelDialog({ + Key? key, + this.onSave, + this.initialSelectedIndex, + required this.spaceModels, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => SpaceModelBloc() + ..add( + SpaceModelSelectedEvent(initialSelectedIndex ?? -1), + ), + child: Builder( + builder: (context) { + final bloc = context.read(); + return AlertDialog( + backgroundColor: ColorsManager.whiteColors, + title: const Text('Link a space model'), + content: spaceModels.isNotEmpty + ? Container( + color: ColorsManager.textFieldGreyColor, + width: MediaQuery.of(context).size.width * 0.7, + height: MediaQuery.of(context).size.height * 0.6, + child: BlocBuilder( + builder: (context, state) { + int selectedIndex = -1; + if (state is SpaceModelSelectedState) { + selectedIndex = state.selectedIndex; + } + return GridView.builder( + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 10.0, + mainAxisSpacing: 10.0, + childAspectRatio: 3, + ), + itemCount: spaceModels.length, + itemBuilder: (BuildContext context, int index) { + final model = spaceModels[index]; + final isSelected = selectedIndex == index; + return GestureDetector( + onTap: () { + bloc.add(SpaceModelSelectedEvent(index)); + }, + child: Container( + margin: const EdgeInsets.all(10.0), + decoration: BoxDecoration( + border: Border.all( + color: isSelected + ? ColorsManager.spaceColor + : Colors.transparent, + width: 2.0, + ), + borderRadius: BorderRadius.circular(8.0), + ), + child: SpaceModelCardWidget(model: model), + ), + ); + }, + ); + }, + ), + ) + : const Text('No space models available.'), + actions: [ + Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CancelButton( + onPressed: () { + Navigator.of(context).pop(); + }, + label: 'Cancel', + ), + const SizedBox(width: 10), + BlocBuilder( + builder: (context, state) { + final isEnabled = state is SpaceModelSelectedState && + state.selectedIndex >= 0; + return SizedBox( + width: 140, + child: DefaultButton( + height: 40, + borderRadius: 10, + onPressed: isEnabled + ? () { + if (onSave != null) { + final selectedModel = + state is SpaceModelSelectedState + ? spaceModels[state.selectedIndex] + : null; + onSave!(selectedModel); + } + Navigator.of(context).pop(); + } + : null, + child: const Text('Save'), + ), + ); + }, + ), + ], + ), + ), + ], + ); + }, + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 6b9818c9..a76543ae 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -94,14 +94,14 @@ class SpaceModelPage extends StatelessWidget { double _calculateChildAspectRatio(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; if (screenWidth > 1600) { - return 2; // Taller cards for larger screens + return 2; } if (screenWidth > 1200) { - return 3; // Adjusted height for medium screens + return 3; } else if (screenWidth > 800) { - return 3.5; // Adjusted height for smaller screens + return 3.5; } else { - return 4.0; // Default ratio for smallest screens + return 4.0; } } From bfbc32d51b24e4d61e72e6d92f20745248dc594a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sat, 11 Jan 2025 18:37:34 +0400 Subject: [PATCH 075/175] moved select space a bit down --- .../all_spaces/widgets/dialogs/create_space_dialog.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index f3d11d91..0b86f6e2 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -82,6 +82,7 @@ class CreateSpaceDialogState extends State { mainAxisAlignment: MainAxisAlignment.center, // crossAxisAlignment: CrossAxisAlignment.center, children: [ + const SizedBox(height: 50), Stack( alignment: Alignment.center, children: [ From 15640ff0df4a7882e0393d5c9f5d9e405a1cd423 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 01:15:44 +0400 Subject: [PATCH 076/175] added space model select --- .../widgets/community_structure_widget.dart | 30 +++-- .../widgets/dialogs/create_space_dialog.dart | 122 +++++++++++++----- .../widgets/subspace_model_create_widget.dart | 1 + 3 files changed, 107 insertions(+), 46 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 9225f5a9..f48e2470 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -29,7 +29,6 @@ class CommunityStructureArea extends StatefulWidget { final List spaces; final List? spaceModels; - CommunityStructureArea({ this.selectedCommunity, this.selectedSpace, @@ -53,7 +52,6 @@ class _CommunityStructureAreaState extends State { bool isEditingName = false; late TransformationController _transformationController; - @override void initState() { super.initState(); @@ -292,21 +290,24 @@ class _CommunityStructureAreaState extends State { products: widget.products, spaceModels: widget.spaceModels, parentSpace: parentIndex != null ? spaces[parentIndex] : null, - onCreateSpace: (String name, String icon, - List selectedProducts) { + onCreateSpace: (String name, + String icon, + List selectedProducts, + SpaceTemplateModel? spaceModel) { setState(() { // Set the first space in the center or use passed position Offset centerPosition = position ?? _getCenterPosition(screenSize); SpaceModel newSpace = SpaceModel( - name: name, - icon: icon, - position: centerPosition, - isPrivate: false, - children: [], - status: SpaceStatus.newSpace, - ); + name: name, + icon: icon, + position: centerPosition, + isPrivate: false, + children: [], + status: SpaceStatus.newSpace, + spaceModel: spaceModel, + ); if (parentIndex != null && direction != null) { SpaceModel parentSpace = spaces[parentIndex]; @@ -342,12 +343,15 @@ class _CommunityStructureAreaState extends State { icon: space.icon, editSpace: space, isEdit: true, - onCreateSpace: (String name, String icon, - List selectedProducts) { + onCreateSpace: (String name, + String icon, + List selectedProducts, + SpaceTemplateModel? spaceModel) { setState(() { // Update the space's properties space.name = name; space.icon = icon; + space.spaceModel = spaceModel; if (space.status != SpaceStatus.newSpace) { space.status = SpaceStatus.modified; // Mark as modified diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 0b86f6e2..e46b0d9e 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -14,8 +14,8 @@ import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/space_icon_const.dart'; class CreateSpaceDialog extends StatefulWidget { - final Function(String, String, List selectedProducts) - onCreateSpace; + final Function(String, String, List selectedProducts, + SpaceTemplateModel? spaceModel) onCreateSpace; final List? products; final String? name; final String? icon; @@ -43,6 +43,7 @@ class CreateSpaceDialog extends StatefulWidget { class CreateSpaceDialogState extends State { String selectedIcon = Assets.location; + SpaceTemplateModel? selectedSpaceModel; String enteredName = ''; List selectedProducts = []; late TextEditingController nameController; @@ -201,41 +202,93 @@ class CreateSpaceDialogState extends State { ), ), const SizedBox(height: 10), - DefaultButton( - onPressed: () { - _showLinkSpaceModelDialog(context); - }, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, + if (selectedSpaceModel == null) + DefaultButton( + onPressed: () { + _showLinkSpaceModelDialog(context); + }, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: Colors.black, + borderColor: ColorsManager.neutralGray, + borderRadius: 16.0, + padding: 10.0, // Reduced padding for smaller size + child: Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + Assets.link, + width: + screenWidth * 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), + ), + const SizedBox(width: 3), + Flexible( + child: Text( + 'Link a space model', + overflow: + TextOverflow.ellipsis, // Prevent overflow + style: Theme.of(context).textTheme.bodyMedium, + ), + ), + ], + ), + ), + ), + if (selectedSpaceModel != null) + 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: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.link, - width: screenWidth * 0.015, // Adjust icon size - height: screenWidth * 0.015, + Chip( + label: Text( + selectedSpaceModel?.modelName ?? '', + style: const TextStyle( + color: ColorsManager.spaceColor), ), - ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Link a space model', - overflow: - TextOverflow.ellipsis, // Prevent overflow - style: Theme.of(context).textTheme.bodyMedium, + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: const BorderSide( + color: 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: () => setState(() { + this.selectedSpaceModel = null; + })), + ], ), ), - ), const SizedBox(height: 25), const Row( children: [ @@ -374,8 +427,8 @@ class CreateSpaceDialogState extends State { ? enteredName : (widget.name ?? ''); if (newName.isNotEmpty) { - widget.onCreateSpace( - newName, selectedIcon, selectedProducts); + widget.onCreateSpace(newName, selectedIcon, + selectedProducts, selectedSpaceModel); Navigator.of(context).pop(); } } @@ -427,6 +480,9 @@ class CreateSpaceDialogState extends State { spaceModels: widget.spaceModels ?? [], onSave: (selectedModel) { if (selectedModel != null) { + setState(() { + this.selectedSpaceModel = selectedModel; + }); print('Selected Model: ${selectedModel.modelName}'); } else { print('No model selected'); diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 22444c3d..832c9ea3 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -134,6 +134,7 @@ class SubspaceModelCreate extends StatelessWidget { ), ), ), + ); } } From cfc1b544b7e0582884bd7a9d196f04a69a76ce75 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 09:10:33 +0400 Subject: [PATCH 077/175] added subspaces --- .../all_spaces/model/space_model.dart | 43 +++ .../all_spaces/model/subspace_model.dart | 110 ++++++ .../all_spaces/model/tag.dart | 61 ++++ .../widgets/dialogs/create_space_dialog.dart | 341 ++++++++++++------ .../create_subspace/bloc/subspace_bloc.dart | 57 +++ .../create_subspace/bloc/subspace_event.dart | 18 + .../create_subspace/bloc/subspace_state.dart | 26 ++ .../views/create_subspace_model_dialog.dart | 196 ++++++++++ .../view/link_space_model_dialog.dart | 5 +- 9 files changed, 735 insertions(+), 122 deletions(-) create mode 100644 lib/pages/spaces_management/all_spaces/model/subspace_model.dart create mode 100644 lib/pages/spaces_management/all_spaces/model/tag.dart create mode 100644 lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart create mode 100644 lib/pages/spaces_management/create_subspace/bloc/subspace_event.dart create mode 100644 lib/pages/spaces_management/create_subspace/bloc/subspace_state.dart create mode 100644 lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart diff --git a/lib/pages/spaces_management/all_spaces/model/space_model.dart b/lib/pages/spaces_management/all_spaces/model/space_model.dart index ba5d83d6..64a98803 100644 --- a/lib/pages/spaces_management/all_spaces/model/space_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/space_model.dart @@ -1,6 +1,8 @@ import 'dart:ui'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:uuid/uuid.dart'; @@ -22,6 +24,8 @@ class SpaceModel { SpaceStatus status; String internalId; SpaceTemplateModel? spaceModel; + final List? tags; + List? subspaces; List outgoingConnections = []; // Connections from this space Connection? incomingConnection; // Connections to this space @@ -42,6 +46,8 @@ class SpaceModel { this.incomingConnection, this.status = SpaceStatus.unchanged, this.spaceModel, + this.tags, + this.subspaces, }) : internalId = internalId ?? const Uuid().v4(); factory SpaceModel.fromJson(Map json, @@ -64,6 +70,11 @@ class SpaceModel { name: json['spaceName'], isPrivate: json['isPrivate'] ?? false, invitationCode: json['invitationCode'], + subspaces: (json['subspaces'] as List?) + ?.where((e) => e is Map) // Validate type + .map((e) => SubspaceModel.fromJson(e as Map)) + .toList() ?? + [], parent: parentInternalId != null ? SpaceModel( internalId: parentInternalId, @@ -85,6 +96,11 @@ class SpaceModel { icon: json['icon'] ?? Assets.location, position: Offset(json['x'] ?? 0, json['y'] ?? 0), isHovered: false, + tags: (json['tags'] as List?) + ?.where((item) => item is Map) // Validate type + .map((item) => Tag.fromJson(item as Map)) + .toList() ?? + [], ); if (json['incomingConnections'] != null && @@ -110,6 +126,7 @@ class SpaceModel { 'isPrivate': isPrivate, 'invitationCode': invitationCode, 'parent': parent?.uuid, + 'subspaces': subspaces?.map((e) => e.toJson()).toList(), 'community': community?.toMap(), 'children': children.map((child) => child.toMap()).toList(), 'icon': icon, @@ -117,6 +134,7 @@ class SpaceModel { 'isHovered': isHovered, 'outgoingConnections': outgoingConnections.map((c) => c.toMap()).toList(), 'incomingConnection': incomingConnection?.toMap(), + 'tags': tags?.map((e) => e.toJson()).toList(), }; } @@ -124,3 +142,28 @@ class SpaceModel { outgoingConnections.add(connection); } } + +extension SpaceExtensions on SpaceModel { + List listAllTagValues() { + final List tagValues = []; + + if (tags != null) { + tagValues.addAll( + tags!.map((tag) => tag.tag ?? '').where((tag) => tag.isNotEmpty)); + } + + if (subspaces != null) { + for (final subspace in subspaces!) { + if (subspace.tags != null) { + tagValues.addAll( + subspace.tags! + .map((tag) => tag.tag ?? '') + .where((tag) => tag.isNotEmpty), + ); + } + } + } + + return tagValues; + } +} diff --git a/lib/pages/spaces_management/all_spaces/model/subspace_model.dart b/lib/pages/spaces_management/all_spaces/model/subspace_model.dart new file mode 100644 index 00000000..2c86523f --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/model/subspace_model.dart @@ -0,0 +1,110 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/utils/constants/action_enum.dart'; + +import 'tag.dart'; + +class SubspaceModel { + final String? uuid; + String subspaceName; + final bool disabled; + List? tags; + + SubspaceModel({ + this.uuid, + required this.subspaceName, + required this.disabled, + this.tags, + }); + + factory SubspaceModel.fromJson(Map json) { + return SubspaceModel( + uuid: json['uuid'] ?? '', + subspaceName: json['subspaceName'] ?? '', + disabled: json['disabled'] ?? false, + tags: (json['tags'] as List?) + ?.map((item) => Tag.fromJson(item)) + .toList() ?? + [], + ); + } + + Map toJson() { + return { + 'uuid': uuid, + 'subspaceName': subspaceName, + 'disabled': disabled, + 'tags': tags?.map((e) => e.toJson()).toList() ?? [], + }; + } +} + +class UpdateSubspaceModel { + final String uuid; + final Action action; + final String? subspaceName; + final List? tags; + UpdateSubspaceModel({ + required this.action, + required this.uuid, + this.subspaceName, + this.tags, + }); + + factory UpdateSubspaceModel.fromJson(Map json) { + return UpdateSubspaceModel( + action: ActionExtension.fromValue(json['action']), + uuid: json['uuid'] ?? '', + subspaceName: json['subspaceName'] ?? '', + tags: (json['tags'] as List) + .map((item) => UpdateTag.fromJson(item)) + .toList(), + ); + } + + Map toJson() { + return { + 'action': action.value, + 'uuid': uuid, + 'subspaceName': subspaceName, + 'tags': tags?.map((e) => e.toJson()).toList() ?? [], + }; + } +} + +class UpdateTag { + final Action action; + final String? uuid; + final String tag; + final bool disabled; + final ProductModel? product; + + UpdateTag({ + required this.action, + this.uuid, + required this.tag, + required this.disabled, + this.product, + }); + + factory UpdateTag.fromJson(Map json) { + return UpdateTag( + action: ActionExtension.fromValue(json['action']), + uuid: json['uuid'] ?? '', + tag: json['tag'] ?? '', + disabled: json['disabled'] ?? false, + product: json['product'] != null + ? ProductModel.fromMap(json['product']) + : null, + ); + } + + Map toJson() { + return { + 'action': action.value, + 'uuid': uuid, + 'tag': tag, + 'disabled': disabled, + 'product': product?.toMap(), + }; + } +} \ No newline at end of file diff --git a/lib/pages/spaces_management/all_spaces/model/tag.dart b/lib/pages/spaces_management/all_spaces/model/tag.dart new file mode 100644 index 00000000..b70865c7 --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/model/tag.dart @@ -0,0 +1,61 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; +import 'package:uuid/uuid.dart'; + +class Tag { + String? uuid; + String? tag; + final ProductModel? product; + String internalId; + String? location; + + Tag( + {this.uuid, + required this.tag, + this.product, + String? internalId, + this.location}) + : internalId = internalId ?? const Uuid().v4(); + + factory Tag.fromJson(Map json) { + final String internalId = json['internalId'] ?? const Uuid().v4(); + + return Tag( + uuid: json['uuid'] ?? '', + internalId: internalId, + tag: json['tag'] ?? '', + product: json['product'] != null + ? ProductModel.fromMap(json['product']) + : null, + ); + } + + Tag copyWith({ + String? tag, + ProductModel? product, + String? location, + }) { + return Tag( + tag: tag ?? this.tag, + product: product ?? this.product, + location: location ?? this.location, + ); + } + + Map toJson() { + return { + 'uuid': uuid, + 'tag': tag, + 'product': product?.toMap(), + }; + } +} + +extension TagModelExtensions on Tag { + TagBodyModel toTagBodyModel() { + return TagBodyModel() + ..uuid = uuid ?? '' + ..tag = tag ?? '' + ..productUuid = product?.uuid; + } +} diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index e46b0d9e..ea6363de 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -5,8 +5,11 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -24,6 +27,7 @@ class CreateSpaceDialog extends StatefulWidget { final SpaceModel? parentSpace; final SpaceModel? editSpace; final List? spaceModels; + final List? subspaces; const CreateSpaceDialog( {super.key, @@ -35,7 +39,8 @@ class CreateSpaceDialog extends StatefulWidget { this.isEdit = false, this.editSpace, this.selectedProducts = const [], - this.spaceModels}); + this.spaceModels, + this.subspaces}); @override CreateSpaceDialogState createState() => CreateSpaceDialogState(); @@ -50,6 +55,7 @@ class CreateSpaceDialogState extends State { bool isOkButtonEnabled = false; bool isNameFieldInvalid = false; bool isNameFieldExist = false; + List? subspaces; @override void initState() { @@ -202,93 +208,93 @@ class CreateSpaceDialogState extends State { ), ), const SizedBox(height: 10), - if (selectedSpaceModel == null) - DefaultButton( - onPressed: () { - _showLinkSpaceModelDialog(context); - }, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.link, - width: - screenWidth * 0.015, // Adjust icon size - height: screenWidth * 0.015, - ), - ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Link a space model', - overflow: - TextOverflow.ellipsis, // Prevent overflow - style: Theme.of(context).textTheme.bodyMedium, - ), - ), - ], - ), - ), - ), - if (selectedSpaceModel != null) - 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: [ - Chip( - label: Text( - selectedSpaceModel?.modelName ?? '', - style: const TextStyle( - color: ColorsManager.spaceColor), - ), - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: const BorderSide( - color: ColorsManager.transparentColor, - width: 0, - ), - ), - deleteIcon: Container( - width: 24, - height: 24, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: ColorsManager.lightGrayColor, - width: 1.5, + selectedSpaceModel == null + ? DefaultButton( + onPressed: () { + _showLinkSpaceModelDialog(context); + }, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: Colors.black, + borderColor: ColorsManager.neutralGray, + borderRadius: 16.0, + padding: 10.0, // Reduced padding for smaller size + child: Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + Assets.link, + width: screenWidth * + 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), ), - ), - child: const Icon( - Icons.close, - size: 16, - color: ColorsManager.lightGrayColor, - ), + const SizedBox(width: 3), + Flexible( + child: Text( + 'Link a space model', + overflow: TextOverflow + .ellipsis, // Prevent overflow + style: Theme.of(context) + .textTheme + .bodyMedium, + ), + ), + ], ), - onDeleted: () => setState(() { - this.selectedSpaceModel = null; - })), - - ], - ), - ), + ), + ) + : 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: [ + Chip( + label: Text( + selectedSpaceModel?.modelName ?? '', + style: const TextStyle( + color: ColorsManager.spaceColor), + ), + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: const BorderSide( + color: 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: () => setState(() { + this.selectedSpaceModel = null; + })), + ], + ), + ), const SizedBox(height: 25), const Row( children: [ @@ -317,39 +323,111 @@ class CreateSpaceDialogState extends State { ], ), const SizedBox(height: 25), - DefaultButton( - onPressed: () {}, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.addIcon, - width: screenWidth * 0.015, // Adjust icon size - height: screenWidth * 0.015, + subspaces == null + ? DefaultButton( + onPressed: () { + _showSubSpaceModelDialog(context, enteredName, [], + false, widget.products, subspaces); + }, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: Colors.black, + borderColor: ColorsManager.neutralGray, + borderRadius: 16.0, + padding: 10.0, // Reduced padding for smaller size + child: Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + Assets.addIcon, + width: screenWidth * + 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), + ), + const SizedBox(width: 3), + Flexible( + child: Text( + 'Create sub space', + overflow: TextOverflow + .ellipsis, // Prevent overflow + style: Theme.of(context) + .textTheme + .bodyMedium, + ), + ), + ], ), ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Create sub space', - overflow: - TextOverflow.ellipsis, // Prevent overflow - style: Theme.of(context).textTheme.bodyMedium, + ) + : SizedBox( + width: screenWidth * 0.35, + child: Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(15), + border: Border.all( + color: ColorsManager.textFieldGreyColor, + width: 3.0, // Border width + ), + ), + child: Wrap( + spacing: 8.0, + runSpacing: 8.0, + children: [ + if (subspaces != null) + ...subspaces!.map( + (subspace) => Chip( + label: Text( + subspace.subspaceName, + style: const TextStyle( + color: ColorsManager + .spaceColor), // Text color + ), + backgroundColor: ColorsManager + .whiteColors, // Chip background color + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 16), // Rounded chip + side: const BorderSide( + color: ColorsManager + .spaceColor), // Border color + ), + ), + ), + GestureDetector( + onTap: () async { + _showSubSpaceModelDialog( + context, + enteredName, + [], + false, + widget.products, + subspaces); + }, + child: Chip( + label: const Text( + 'Edit', + style: TextStyle( + color: ColorsManager.spaceColor), + ), + backgroundColor: + ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: const BorderSide( + color: ColorsManager.spaceColor), + ), + ), + ), + ], ), ), - ], - ), - ), - ), + ), const SizedBox(height: 10), DefaultButton( onPressed: () { @@ -481,15 +559,40 @@ class CreateSpaceDialogState extends State { onSave: (selectedModel) { if (selectedModel != null) { setState(() { - this.selectedSpaceModel = selectedModel; + selectedSpaceModel = selectedModel; }); - print('Selected Model: ${selectedModel.modelName}'); - } else { - print('No model selected'); } }, ); }, ); } + + void _showSubSpaceModelDialog( + BuildContext context, + String name, + final List? spaceTags, + bool isEdit, + List? products, + final List? existingSubSpaces) { + showDialog( + context: context, + builder: (BuildContext context) { + return CreateSubSpaceDialog( + spaceName: name, + dialogTitle: 'Create Sub-space', + spaceTags: spaceTags, + isEdit: isEdit, + products: products, + existingSubSpaces: existingSubSpaces, + onSave: (slectedSubspaces) { + if (slectedSubspaces != null) { + setState(() { + subspaces = slectedSubspaces; + }); + } + }); + }, + ); + } } diff --git a/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart b/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart new file mode 100644 index 00000000..6a072e4a --- /dev/null +++ b/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart @@ -0,0 +1,57 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; +import 'package:syncrow_web/utils/constants/action_enum.dart'; + +import 'subspace_event.dart'; +import 'subspace_state.dart'; + +class SubSpaceBloc extends Bloc { + SubSpaceBloc() : super(SubSpaceState([], [], '')) { + on((event, emit) { + final existingNames = + state.subSpaces.map((e) => e.subspaceName).toSet(); + + if (existingNames.contains(event.subSpace.subspaceName.toLowerCase())) { + emit(SubSpaceState( + state.subSpaces, + state.updatedSubSpaceModels, + 'Subspace name already exists.', + )); + } else { + final updatedSubSpaces = List.from(state.subSpaces) + ..add(event.subSpace); + + emit(SubSpaceState( + updatedSubSpaces, + state.updatedSubSpaceModels, + '', + )); + } + }); + + // Handle RemoveSubSpace Event + on((event, emit) { + final updatedSubSpaces = List.from(state.subSpaces) + ..remove(event.subSpace); + + final updatedSubspaceModels = List.from( + state.updatedSubSpaceModels, + ); + + if (event.subSpace.uuid?.isNotEmpty ?? false) { + updatedSubspaceModels.add(UpdateSubspaceModel( + action: Action.delete, + uuid: event.subSpace.uuid!, + )); + } + + emit(SubSpaceState( + updatedSubSpaces, + updatedSubspaceModels, + '', // Clear error message + )); + }); + + // Handle UpdateSubSpace Event + } +} diff --git a/lib/pages/spaces_management/create_subspace/bloc/subspace_event.dart b/lib/pages/spaces_management/create_subspace/bloc/subspace_event.dart new file mode 100644 index 00000000..cdb62ea1 --- /dev/null +++ b/lib/pages/spaces_management/create_subspace/bloc/subspace_event.dart @@ -0,0 +1,18 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; + +abstract class SubSpaceEvent {} + +class AddSubSpace extends SubSpaceEvent { + final SubspaceModel subSpace; + AddSubSpace(this.subSpace); +} + +class RemoveSubSpace extends SubSpaceEvent { + final SubspaceModel subSpace; + RemoveSubSpace(this.subSpace); +} + +class UpdateSubSpace extends SubSpaceEvent { + final SubspaceModel updatedSubSpace; + UpdateSubSpace(this.updatedSubSpace); +} diff --git a/lib/pages/spaces_management/create_subspace/bloc/subspace_state.dart b/lib/pages/spaces_management/create_subspace/bloc/subspace_state.dart new file mode 100644 index 00000000..d1374ea1 --- /dev/null +++ b/lib/pages/spaces_management/create_subspace/bloc/subspace_state.dart @@ -0,0 +1,26 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; + +class SubSpaceState { + final List subSpaces; + final List updatedSubSpaceModels; + final String errorMessage; + + SubSpaceState( + this.subSpaces, + this.updatedSubSpaceModels, + this.errorMessage, + ); + + + SubSpaceState copyWith({ + List? subSpaces, + List? updatedSubSpaceModels, + String? errorMessage, + }) { + return SubSpaceState( + subSpaces ?? this.subSpaces, + updatedSubSpaceModels ?? this.updatedSubSpaceModels, + errorMessage ?? this.errorMessage, + ); + } +} diff --git a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart new file mode 100644 index 00000000..6fd0b936 --- /dev/null +++ b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart @@ -0,0 +1,196 @@ +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/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_event.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_state.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class CreateSubSpaceDialog extends StatelessWidget { + final bool isEdit; + final String dialogTitle; + final List? existingSubSpaces; + final String? spaceName; + final List? spaceTags; + final List? products; + final Function(List?)? onSave; + + const CreateSubSpaceDialog( + {Key? key, + required this.isEdit, + required this.dialogTitle, + this.existingSubSpaces, + required this.spaceName, + required this.spaceTags, + required this.products, + required this.onSave}) + : super(key: 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: (_) { + final bloc = SubSpaceBloc(); + if (existingSubSpaces != null) { + for (var subSpace in existingSubSpaces!) { + bloc.add(AddSubSpace(subSpace)); + } + } + return bloc; + }, + child: BlocBuilder( + builder: (context, state) { + return Container( + color: ColorsManager.whiteColors, + child: SizedBox( + width: screenWidth * 0.35, + 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.map( + (subSpace) => Chip( + label: Text( + subSpace.subspaceName, + style: const TextStyle( + color: ColorsManager.spaceColor), + ), + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: const BorderSide( + color: 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(RemoveSubSpace(subSpace)), + ), + ), + SizedBox( + width: 200, + child: TextField( + controller: textController, + decoration: InputDecoration( + border: InputBorder.none, + hintText: state.subSpaces.isEmpty + ? 'Please enter the name' + : null, + hintStyle: const TextStyle( + color: ColorsManager.lightGrayColor), + ), + onSubmitted: (value) { + if (value.trim().isNotEmpty) { + context.read().add( + AddSubSpace(SubspaceModel( + subspaceName: value.trim(), + disabled: false))); + textController.clear(); + } + }, + style: const TextStyle( + color: ColorsManager.blackColor), + ), + ), + if (state.errorMessage.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + state.errorMessage, + style: const TextStyle( + color: ColorsManager.warningRed, + fontSize: 12, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: CancelButton( + label: 'Cancel', + onPressed: () async {}, + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + onPressed: () async { + final subSpaces = context + .read() + .state + .subSpaces; + onSave!(subSpaces); + Navigator.of(context).pop(); + }, + backgroundColor: ColorsManager.secondaryColor, + borderRadius: 10, + foregroundColor: ColorsManager.whiteColors, + child: const Text('OK'), + ), + ), + ], + ), + ], + ), + ), + )); + }, + ), + ), + ); + } +} 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 44cbbc67..2ab969df 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 @@ -106,9 +106,8 @@ class LinkSpaceModelDialog extends StatelessWidget { ? () { if (onSave != null) { final selectedModel = - state is SpaceModelSelectedState - ? spaceModels[state.selectedIndex] - : null; + spaceModels[state.selectedIndex]; + onSave!(selectedModel); } Navigator.of(context).pop(); From 6591ef16648d1d57f34edb7abb5bcc8df4905786 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 09:25:29 +0400 Subject: [PATCH 078/175] load space models --- .../bloc/space_management_bloc.dart | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index f8ed1b86..554fdafe 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -52,10 +52,14 @@ class SpaceManagementBloc break; } } + + var prevSpaceModels = await fetchSpaceModels(previousState); + emit(SpaceManagementLoaded( communities: updatedCommunities, products: previousState.products, selectedCommunity: previousState.selectedCommunity, + spaceModels: prevSpaceModels, )); } } else { @@ -66,6 +70,23 @@ class SpaceManagementBloc } } + Future> fetchSpaceModels( + SpaceManagementState previousState) async { + List prevSpaceModels = []; + + if (previousState is SpaceManagementLoaded || previousState is BlankState) { + prevSpaceModels = List.from( + (previousState as dynamic).spaceModels ?? [], + ); + } + + if (prevSpaceModels.isEmpty) { + prevSpaceModels = await _spaceModelApi.listSpaceModels(page: 1); + } + + return prevSpaceModels; + } + void _onloadProducts() async { if (_cachedProducts == null) { final products = await _productApi.fetchProducts(); @@ -89,19 +110,24 @@ class SpaceManagementBloc return await _api.getSpaceHierarchy(communityUuid); } - void _onNewCommunity( + Future _onNewCommunity( NewCommunityEvent event, Emitter emit, - ) { + ) async { try { + final previousState = state; + if (event.communities.isEmpty) { emit(const SpaceManagementError('No communities provided.')); return; } + var prevSpaceModels = await fetchSpaceModels(previousState); + emit(BlankState( communities: event.communities, products: _cachedProducts ?? [], + spaceModels: prevSpaceModels, )); } catch (error) { emit(SpaceManagementError('Error loading communities: $error')); @@ -112,6 +138,7 @@ class SpaceManagementBloc BlankStateEvent event, Emitter emit) async { try { final previousState = state; + var prevSpaceModels = await fetchSpaceModels(previousState); if (previousState is SpaceManagementLoaded || previousState is BlankState) { @@ -119,6 +146,7 @@ class SpaceManagementBloc emit(BlankState( communities: List.from(prevCommunities), products: _cachedProducts ?? [], + spaceModels: prevSpaceModels, )); return; } @@ -139,6 +167,7 @@ class SpaceManagementBloc })); emit(BlankState( + spaceModels: prevSpaceModels, communities: updatedCommunities, products: _cachedProducts ?? [], )); @@ -217,6 +246,7 @@ class SpaceManagementBloc try { CommunityModel? newCommunity = await _api.createCommunity(event.name, event.description); + var prevSpaceModels = await fetchSpaceModels(previousState); if (newCommunity != null) { if (previousState is SpaceManagementLoaded || @@ -226,6 +256,7 @@ class SpaceManagementBloc ); final updatedCommunities = prevCommunities..add(newCommunity); emit(SpaceManagementLoaded( + spaceModels: prevSpaceModels, communities: updatedCommunities, products: _cachedProducts ?? [], selectedCommunity: newCommunity, From 3c5e0a777814018cfa1dae1bdea99885952c815d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 10:04:44 +0400 Subject: [PATCH 079/175] device type select --- .../bloc/add_device_model_bloc.dart | 38 +++++ .../bloc/add_device_type_model_event.dart | 19 +++ .../views/add_device_type_model_widget.dart | 130 +++++++++++++++ .../widgets/device_type_tile_widget.dart | 67 ++++++++ .../widgets/scrollable_grid_view_widget.dart | 70 ++++++++ .../widgets/dialogs/create_space_dialog.dart | 154 ++++++++++++------ .../widgets/tag_chips_display_widget.dart | 1 + .../views/add_device_type_model_widget.dart | 2 - 8 files changed, 433 insertions(+), 48 deletions(-) create mode 100644 lib/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart create mode 100644 lib/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart create mode 100644 lib/pages/spaces_management/add_device_type/views/add_device_type_model_widget.dart create mode 100644 lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart create mode 100644 lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart diff --git a/lib/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart b/lib/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart new file mode 100644 index 00000000..10f3327e --- /dev/null +++ b/lib/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart @@ -0,0 +1,38 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart'; + +class AddDeviceTypeBloc + extends Bloc> { + AddDeviceTypeBloc(List initialProducts) + : super(initialProducts) { + on(_onUpdateProductCount); + } + + void _onUpdateProductCount( + UpdateProductCountEvent event, Emitter> emit) { + final existingProduct = state.firstWhere( + (p) => p.productId == event.productId, + orElse: () => SelectedProduct(productId: event.productId, count: 0,productName: event.productName,product: event.product ), + ); + + if (event.count > 0) { + if (!state.contains(existingProduct)) { + emit([ + ...state, + SelectedProduct(productId: event.productId, count: event.count, productName: event.productName, product: event.product) + ]); + } else { + final updatedList = state.map((p) { + if (p.productId == event.productId) { + return SelectedProduct(productId: p.productId, count: event.count, productName: p.productName,product: p.product); + } + return p; + }).toList(); + emit(updatedList); + } + } else { + emit(state.where((p) => p.productId != event.productId).toList()); + } + } +} diff --git a/lib/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart b/lib/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart new file mode 100644 index 00000000..addb6d67 --- /dev/null +++ b/lib/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart @@ -0,0 +1,19 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; + +abstract class AddDeviceTypeEvent extends Equatable { + @override + List get props => []; +} + +class UpdateProductCountEvent extends AddDeviceTypeEvent { + final String productId; + final int count; + final String productName; + final ProductModel product; + + UpdateProductCountEvent({required this.productId, required this.count, required this.productName, required this.product}); + + @override + List get props => [productId, count]; +} diff --git a/lib/pages/spaces_management/add_device_type/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/add_device_type/views/add_device_type_model_widget.dart new file mode 100644 index 00000000..38f5650b --- /dev/null +++ b/lib/pages/spaces_management/add_device_type/views/add_device_type_model_widget.dart @@ -0,0 +1,130 @@ +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/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +import '../bloc/add_device_model_bloc.dart'; + +class AddDeviceTypeWidget extends StatelessWidget { + final List? products; + final ValueChanged>? onProductsSelected; + final List? initialSelectedProducts; + final List? subspaces; + final List? spaceTags; + final List? allTags; + final String spaceName; + + const AddDeviceTypeWidget( + {super.key, + this.products, + this.initialSelectedProducts, + this.onProductsSelected, + this.subspaces, + this.allTags, + this.spaceTags, + required this.spaceName}); + + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + final crossAxisCount = size.width > 1200 + ? 8 + : size.width > 800 + ? 5 + : 3; + + return BlocProvider( + create: (_) => AddDeviceTypeBloc(initialSelectedProducts ?? []), + child: Builder( + builder: (context) => AlertDialog( + title: const Text('Add Devices'), + backgroundColor: ColorsManager.whiteColors, + content: SingleChildScrollView( + child: Container( + width: size.width * 0.9, + height: size.height * 0.65, + color: ColorsManager.textFieldGreyColor, + child: Column( + children: [ + const SizedBox(height: 16), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: ScrollableGridViewWidget( + products: products, crossAxisCount: crossAxisCount), + ), + ), + ], + ), + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CancelButton( + label: 'Cancel', + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + ActionButton( + label: 'Continue', + backgroundColor: ColorsManager.secondaryColor, + foregroundColor: ColorsManager.whiteColors, + onPressed: () async { + final currentState = + context.read().state; + Navigator.of(context).pop(); + + if (currentState.isNotEmpty) { + final initialTags = generateInitialTags( + spaceTags: spaceTags, + subspaces: subspaces, + ); + + final dialogTitle = initialTags.isNotEmpty + ? 'Edit Device' + : 'Assign Tags'; + } + }, + ), + ], + ), + ], + ), + )); + } + + List generateInitialTags({ + List? spaceTags, + List? subspaces, + }) { + final List initialTags = []; + + if (spaceTags != null) { + initialTags.addAll(spaceTags); + } + + if (subspaces != null) { + for (var subspace in subspaces) { + if (subspace.tags != null) { + initialTags.addAll( + subspace.tags!.map( + (tag) => tag.copyWith(location: subspace.subspaceName), + ), + ); + } + } + } + + return initialTags; + } +} diff --git a/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart b/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart new file mode 100644 index 00000000..2feea7d9 --- /dev/null +++ b/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_icon_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_name_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class DeviceTypeTileWidget extends StatelessWidget { + final ProductModel product; + final List productCounts; + + const DeviceTypeTileWidget({ + super.key, + required this.product, + required this.productCounts, + }); + + @override + Widget build(BuildContext context) { + final selectedProduct = productCounts.firstWhere( + (p) => p.productId == product.uuid, + orElse: () => SelectedProduct( + productId: product.uuid, + count: 0, + productName: product.catName, + product: product), + ); + + return Card( + elevation: 2, + color: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.all(4.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + DeviceIconWidget(icon: product.icon ?? Assets.doorSensor), + const SizedBox(height: 4), + DeviceNameWidget(name: product.name), + const SizedBox(height: 4), + CounterWidget( + initialCount: selectedProduct.count, + onCountChanged: (newCount) { + context.read().add( + UpdateProductCountEvent( + productId: product.uuid, + count: newCount, + productName: product.catName, + product: product), + ); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart b/lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart new file mode 100644 index 00000000..aeee6f1b --- /dev/null +++ b/lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; + +class ScrollableGridViewWidget extends StatelessWidget { + final List? products; + final int crossAxisCount; + final List? initialProductCounts; + + const ScrollableGridViewWidget({ + super.key, + required this.products, + required this.crossAxisCount, + this.initialProductCounts, + }); + + @override + Widget build(BuildContext context) { + final scrollController = ScrollController(); + + return Scrollbar( + controller: scrollController, + thumbVisibility: true, + child: BlocBuilder>( + builder: (context, productCounts) { + return GridView.builder( + controller: scrollController, + shrinkWrap: true, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + mainAxisSpacing: 6, + crossAxisSpacing: 4, + childAspectRatio: .8, + ), + itemCount: products?.length ?? 0, + itemBuilder: (context, index) { + final product = products![index]; + final initialProductCount = _findInitialProductCount(product); + + return DeviceTypeTileWidget( + product: product, + productCounts: initialProductCount != null + ? [...productCounts, initialProductCount] + : productCounts, + ); + }, + ); + }, + ), + ); + } + + SelectedProduct? _findInitialProductCount(ProductModel product) { + if (initialProductCounts == null) return null; + final matchingProduct = initialProductCounts!.firstWhere( + (selectedProduct) => selectedProduct.productId == product.uuid, + orElse: () => SelectedProduct( + productId: '', + count: 0, + productName: '', + product: null, + ), + ); + + return matchingProduct.productId.isNotEmpty ? matchingProduct : null; + } +} diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index ea6363de..e7e103e3 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.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/add_device_type/views/add_device_type_model_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; @@ -28,6 +29,7 @@ class CreateSpaceDialog extends StatefulWidget { final SpaceModel? editSpace; final List? spaceModels; final List? subspaces; + final List? tags; const CreateSpaceDialog( {super.key, @@ -40,7 +42,8 @@ class CreateSpaceDialog extends StatefulWidget { this.editSpace, this.selectedProducts = const [], this.spaceModels, - this.subspaces}); + this.subspaces, + this.tags}); @override CreateSpaceDialogState createState() => CreateSpaceDialogState(); @@ -56,6 +59,7 @@ class CreateSpaceDialogState extends State { bool isNameFieldInvalid = false; bool isNameFieldExist = false; List? subspaces; + List? tags; @override void initState() { @@ -326,7 +330,7 @@ class CreateSpaceDialogState extends State { subspaces == null ? DefaultButton( onPressed: () { - _showSubSpaceModelDialog(context, enteredName, [], + _showSubSpaceDialog(context, enteredName, [], false, widget.products, subspaces); }, backgroundColor: ColorsManager.textFieldGreyColor, @@ -401,7 +405,7 @@ class CreateSpaceDialogState extends State { ), GestureDetector( onTap: () async { - _showSubSpaceModelDialog( + _showSubSpaceDialog( context, enteredName, [], @@ -429,51 +433,50 @@ class CreateSpaceDialogState extends State { ), ), const SizedBox(height: 10), - DefaultButton( - onPressed: () { - showDialog( - context: context, - builder: (context) => AddDeviceWidget( - products: widget.products, - onProductsSelected: (selectedProductsMap) { - setState(() { - selectedProducts = selectedProductsMap; - }); + (tags?.isNotEmpty == true || + subspaces?.any((subspace) => + subspace.tags?.isNotEmpty == true) == + true) + ? Container() + : DefaultButton( + onPressed: () { + _showTagCreateDialog(context, enteredName, tags, + subspaces, widget.products); }, - ), - ); - }, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.addIcon, - width: screenWidth * 0.015, // Adjust icon size - height: screenWidth * 0.015, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: Colors.black, + borderColor: ColorsManager.neutralGray, + borderRadius: 16.0, + padding: 10.0, // Reduced padding for smaller size + child: Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + Assets.addIcon, + width: screenWidth * + 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), + ), + const SizedBox(width: 3), + Flexible( + child: Text( + 'Add devices', + overflow: TextOverflow + .ellipsis, // Prevent overflow + style: Theme.of(context) + .textTheme + .bodyMedium, + ), + ), + ], ), ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Add devices', - overflow: - TextOverflow.ellipsis, // Prevent overflow - style: Theme.of(context).textTheme.bodyMedium, - ), - ), - ], - ), - ), - ), + ) ], ), ), @@ -560,6 +563,7 @@ class CreateSpaceDialogState extends State { if (selectedModel != null) { setState(() { selectedSpaceModel = selectedModel; + subspaces = null; }); } }, @@ -568,7 +572,7 @@ class CreateSpaceDialogState extends State { ); } - void _showSubSpaceModelDialog( + void _showSubSpaceDialog( BuildContext context, String name, final List? spaceTags, @@ -589,10 +593,68 @@ class CreateSpaceDialogState extends State { if (slectedSubspaces != null) { setState(() { subspaces = slectedSubspaces; + selectedSpaceModel = null; }); } }); }, ); } + + void _showTagCreateDialog( + BuildContext context, + String name, + final List? spaceTags, + List? subspaces, + List? products) { + showDialog( + context: context, + builder: (BuildContext context) { + return AddDeviceTypeWidget( + spaceName: name, + products: products, + subspaces: subspaces, + spaceTags: spaceTags, + allTags: [], + initialSelectedProducts: + createInitialSelectedProducts(tags, subspaces), + ); + }, + ); + } + + List createInitialSelectedProducts( + List? tags, List? subspaces) { + final Map productCounts = {}; + + if (tags != null) { + for (var tag in tags) { + if (tag.product != null) { + productCounts[tag.product!] = (productCounts[tag.product!] ?? 0) + 1; + } + } + } + + if (subspaces != null) { + for (var subspace in subspaces) { + if (subspace.tags != null) { + for (var tag in subspace.tags!) { + if (tag.product != null) { + productCounts[tag.product!] = + (productCounts[tag.product!] ?? 0) + 1; + } + } + } + } + } + + return productCounts.entries + .map((entry) => SelectedProduct( + productId: entry.key.uuid, + count: entry.value, + productName: entry.key.name ?? 'Unnamed', + product: entry.key, + )) + .toList(); + } } diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 9cb356ef..799eb71b 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -189,4 +189,5 @@ class TagChipDisplay extends StatelessWidget { )) .toList(); } + } diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index a8a6b1ac..f7f4c3f7 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -15,7 +15,6 @@ import 'package:syncrow_web/utils/color_manager.dart'; class AddDeviceTypeModelWidget extends StatelessWidget { final List? products; - final ValueChanged>? onProductsSelected; final List? initialSelectedProducts; final List? subspaces; final List? spaceTagModels; @@ -26,7 +25,6 @@ class AddDeviceTypeModelWidget extends StatelessWidget { {super.key, this.products, this.initialSelectedProducts, - this.onProductsSelected, this.subspaces, this.allTags, this.spaceTagModels, From 1be52adcc8feea402b562a6888c1e62c3949d83d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 10:43:47 +0400 Subject: [PATCH 080/175] added assign tag --- ...idget.dart => add_device_type_widget.dart} | 21 +- .../widgets/dialogs/create_space_dialog.dart | 106 +++++- .../assign_tag/bloc/assign_tag_bloc.dart | 153 ++++++++ .../assign_tag/bloc/assign_tag_event.dart | 55 +++ .../assign_tag/bloc/assign_tag_state.dart | 38 ++ .../assign_tag/views/assign_tag_dialog.dart | 340 ++++++++++++++++++ 6 files changed, 707 insertions(+), 6 deletions(-) rename lib/pages/spaces_management/add_device_type/views/{add_device_type_model_widget.dart => add_device_type_widget.dart} (82%) create mode 100644 lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart create mode 100644 lib/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart create mode 100644 lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart create mode 100644 lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart diff --git a/lib/pages/spaces_management/add_device_type/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart similarity index 82% rename from lib/pages/spaces_management/add_device_type/views/add_device_type_model_widget.dart rename to lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart index 38f5650b..677b9fb9 100644 --- a/lib/pages/spaces_management/add_device_type/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart @@ -1,16 +1,16 @@ 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/spaces_management/add_device_type/bloc/add_device_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -import '../bloc/add_device_model_bloc.dart'; class AddDeviceTypeWidget extends StatelessWidget { final List? products; @@ -20,6 +20,8 @@ class AddDeviceTypeWidget extends StatelessWidget { final List? spaceTags; final List? allTags; final String spaceName; + final Function(List,List?)? onSave; + const AddDeviceTypeWidget( {super.key, @@ -29,6 +31,7 @@ class AddDeviceTypeWidget extends StatelessWidget { this.subspaces, this.allTags, this.spaceTags, + this.onSave, required this.spaceName}); @override @@ -93,6 +96,20 @@ class AddDeviceTypeWidget extends StatelessWidget { final dialogTitle = initialTags.isNotEmpty ? 'Edit Device' : 'Assign Tags'; + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AssignTagDialog( + products: products, + subspaces: subspaces, + addedProducts: currentState, + allTags: allTags, + spaceName: spaceName, + initialTags: initialTags, + title: dialogTitle, + onSave: onSave, + ), + ); } }, ), diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index e7e103e3..ed4a22b9 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -2,13 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.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/add_device_type/views/add_device_type_model_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart'; @@ -72,7 +71,6 @@ class CreateSpaceDialogState extends State { enteredName.isNotEmpty || nameController.text.isNotEmpty; } - @override @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; @@ -437,7 +435,80 @@ class CreateSpaceDialogState extends State { subspaces?.any((subspace) => subspace.tags?.isNotEmpty == true) == true) - ? Container() + ? SizedBox( + width: screenWidth * 0.25, + child: Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(15), + border: Border.all( + color: ColorsManager.textFieldGreyColor, + width: 3.0, // Border width + ), + ), + child: Wrap( + spacing: 8.0, + runSpacing: 8.0, + children: [ + // Combine tags from spaceModel and subspaces + ..._groupTags([ + ...?tags, + ...?subspaces?.expand( + (subspace) => subspace.tags ?? []) + ]).entries.map( + (entry) => Chip( + avatar: SizedBox( + width: 24, + height: 24, + child: SvgPicture.asset( + entry.key.icon ?? + 'assets/icons/gateway.svg', + fit: BoxFit.contain, + ), + ), + label: Text( + 'x${entry.value}', // Show count + style: const TextStyle( + color: ColorsManager.spaceColor, + ), + ), + backgroundColor: + ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(16), + side: const BorderSide( + color: ColorsManager.spaceColor, + ), + ), + ), + ), + GestureDetector( + onTap: () async { + _showTagCreateDialog(context, enteredName, + tags, subspaces, widget.products); + // Edit action + }, + child: Chip( + label: const Text( + 'Edit', + style: TextStyle( + color: ColorsManager.spaceColor), + ), + backgroundColor: + ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: const BorderSide( + color: ColorsManager.spaceColor), + ), + ), + ), + ], + ), + ), + ) : DefaultButton( onPressed: () { _showTagCreateDialog(context, enteredName, tags, @@ -618,6 +689,23 @@ class CreateSpaceDialogState extends State { allTags: [], initialSelectedProducts: createInitialSelectedProducts(tags, subspaces), + onSave: (selectedSpaceTags, selectedSubspaces) { + setState(() { + if (spaceTags != null) selectedSpaceTags = spaceTags; + if (selectedSubspaces != null) { + if (subspaces != null) { + for (final subspace in subspaces) { + for (final selectedSubspace in selectedSubspaces) { + if (subspace.subspaceName == + selectedSubspace.subspaceName) { + subspace.tags = selectedSubspace.tags; + } + } + } + } + } + }); + }, ); }, ); @@ -657,4 +745,14 @@ class CreateSpaceDialogState extends State { )) .toList(); } + + Map _groupTags(List tags) { + final Map groupedTags = {}; + for (var tag in tags) { + if (tag.product != null) { + groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1; + } + } + return groupedTags; + } } diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart new file mode 100644 index 00000000..6adcc6a7 --- /dev/null +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart @@ -0,0 +1,153 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart'; + +class AssignTagBloc + extends Bloc { + AssignTagBloc() : super(AssignTagInitial()) { + on((event, emit) { + final initialTags = event.initialTags ?? []; + + final existingTagCounts = {}; + for (var tag in initialTags) { + if (tag.product != null) { + existingTagCounts[tag.product!.uuid] = + (existingTagCounts[tag.product!.uuid] ?? 0) + 1; + } + } + + final allTags = []; + + for (var selectedProduct in event.addedProducts) { + final existingCount = existingTagCounts[selectedProduct.productId] ?? 0; + + if (selectedProduct.count == 0 || + selectedProduct.count <= existingCount) { + allTags.addAll(initialTags + .where((tag) => tag.product?.uuid == selectedProduct.productId)); + continue; + } + + final missingCount = selectedProduct.count - existingCount; + + allTags.addAll(initialTags + .where((tag) => tag.product?.uuid == selectedProduct.productId)); + + if (missingCount > 0) { + allTags.addAll(List.generate( + missingCount, + (index) => Tag( + tag: '', + product: selectedProduct.product, + location: 'None', + ), + )); + } + } + + emit(AssignTagLoaded( + tags: allTags, + isSaveEnabled: _validateTags(allTags), + )); + }); + + on((event, emit) { + final currentState = state; + + if (currentState is AssignTagLoaded && + currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags); + tags[event.index].tag = event.tag; + emit(AssignTagLoaded( + tags: tags, + isSaveEnabled: _validateTags(tags), + errorMessage: _getValidationError(tags), + )); + } + }); + + on((event, emit) { + final currentState = state; + + if (currentState is AssignTagLoaded && + currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags); + + // Use copyWith for immutability + tags[event.index] = + tags[event.index].copyWith(location: event.location); + + emit(AssignTagLoaded( + tags: tags, + isSaveEnabled: _validateTags(tags), + )); + } + }); + + on((event, emit) { + final currentState = state; + + if (currentState is AssignTagLoaded && + currentState.tags.isNotEmpty) { + final tags = List.from(currentState.tags); + + emit(AssignTagLoaded( + tags: tags, + isSaveEnabled: _validateTags(tags), + errorMessage: _getValidationError(tags), + )); + } + }); + + on((event, emit) { + final currentState = state; + + if (currentState is AssignTagLoaded && + currentState.tags.isNotEmpty) { + final updatedTags = List.from(currentState.tags) + ..remove(event.tagToDelete); + + emit(AssignTagLoaded( + tags: updatedTags, + isSaveEnabled: _validateTags(updatedTags), + )); + } else { + emit(const AssignTagLoaded( + tags: [], + isSaveEnabled: false, + )); + } + }); + } + + bool _validateTags(List tags) { + if (tags.isEmpty) { + return false; + } + final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); + final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); + return uniqueTags.length == tags.length && !hasEmptyTag; + } + + String? _getValidationError(List tags) { + final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); + if (hasEmptyTag) return 'Tags cannot be empty.'; + final duplicateTags = tags + .map((tag) => tag.tag?.trim() ?? '') + .fold>({}, (map, tag) { + map[tag] = (map[tag] ?? 0) + 1; + return map; + }) + .entries + .where((entry) => entry.value > 1) + .map((entry) => entry.key) + .toList(); + + if (duplicateTags.isNotEmpty) { + return 'Duplicate tags found: ${duplicateTags.join(', ')}'; + } + + return null; + } +} diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart new file mode 100644 index 00000000..9116b094 --- /dev/null +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart @@ -0,0 +1,55 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; + +abstract class AssignTagEvent extends Equatable { + const AssignTagEvent(); + + @override + List get props => []; +} + +class InitializeTags extends AssignTagEvent { + final List? initialTags; + final List addedProducts; + + const InitializeTags({ + required this.initialTags, + required this.addedProducts, + }); + + @override + List get props => [initialTags ?? [], addedProducts]; +} + +class UpdateTagEvent extends AssignTagEvent { + final int index; + final String tag; + + const UpdateTagEvent({required this.index, required this.tag}); + + @override + List get props => [index, tag]; +} + +class UpdateLocation extends AssignTagEvent { + final int index; + final String location; + + const UpdateLocation({required this.index, required this.location}); + + @override + List get props => [index, location]; +} + +class ValidateTags extends AssignTagEvent {} + +class DeleteTag extends AssignTagEvent { + final Tag tagToDelete; + final List tags; + + const DeleteTag({required this.tagToDelete, required this.tags}); + + @override + List get props => [tagToDelete, tags]; +} diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart new file mode 100644 index 00000000..19cf4435 --- /dev/null +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart @@ -0,0 +1,38 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; + +abstract class AssignTagState extends Equatable { + const AssignTagState(); + + @override + List get props => []; +} + +class AssignTagInitial extends AssignTagState {} + +class AssignTagLoading extends AssignTagState {} + +class AssignTagLoaded extends AssignTagState { + final List tags; + final bool isSaveEnabled; + final String? errorMessage; + + const AssignTagLoaded({ + required this.tags, + required this.isSaveEnabled, + this.errorMessage, + }); + + @override + List get props => [tags, isSaveEnabled]; +} + +class AssignTagError extends AssignTagState { + final String errorMessage; + + const AssignTagError(this.errorMessage); + + @override + List get props => [errorMessage]; +} diff --git a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart new file mode 100644 index 00000000..31f9bec1 --- /dev/null +++ b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart @@ -0,0 +1,340 @@ +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/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class AssignTagDialog extends StatelessWidget { + final List? products; + final List? subspaces; + final List? initialTags; + final ValueChanged>? onTagsAssigned; + final List addedProducts; + final List? allTags; + final String spaceName; + final String title; + final Function(List, List?)? onSave; + + const AssignTagDialog( + {Key? key, + required this.products, + required this.subspaces, + required this.addedProducts, + this.initialTags, + this.onTagsAssigned, + this.allTags, + required this.spaceName, + required this.title, + this.onSave}) + : super(key: key); + + @override + Widget build(BuildContext context) { + final List locations = + (subspaces ?? []).map((subspace) => subspace.subspaceName).toList(); + return BlocProvider( + create: (_) => AssignTagBloc() + ..add(InitializeTags( + initialTags: initialTags, + addedProducts: addedProducts, + )), + child: BlocBuilder( + builder: (context, state) { + if (state is AssignTagLoaded) { + final controllers = List.generate( + state.tags.length, + (index) => TextEditingController(text: state.tags[index].tag), + ); + + return AlertDialog( + title: Text(title), + backgroundColor: ColorsManager.whiteColors, + content: SingleChildScrollView( + child: Column( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(20), + child: DataTable( + headingRowColor: WidgetStateProperty.all( + ColorsManager.dataHeaderGrey), + border: TableBorder.all( + color: ColorsManager.dataHeaderGrey, + width: 1, + borderRadius: BorderRadius.circular(20), + ), + columns: [ + DataColumn( + label: Text('#', + style: + Theme.of(context).textTheme.bodyMedium)), + DataColumn( + label: Text('Device', + style: + Theme.of(context).textTheme.bodyMedium)), + DataColumn( + label: Text('Tag', + style: + Theme.of(context).textTheme.bodyMedium)), + DataColumn( + label: Text('Location', + style: + Theme.of(context).textTheme.bodyMedium)), + ], + rows: state.tags.isEmpty + ? [ + const DataRow(cells: [ + DataCell( + Center( + child: Text( + 'No Data Available', + style: TextStyle( + fontSize: 14, + color: ColorsManager.lightGrayColor, + ), + ), + ), + ), + DataCell(SizedBox()), + DataCell(SizedBox()), + DataCell(SizedBox()), + ]) + ] + : List.generate(state.tags.length, (index) { + final tag = state.tags[index]; + final controller = controllers[index]; + + return DataRow( + cells: [ + DataCell(Text(index.toString())), + DataCell( + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + tag.product?.name ?? 'Unknown', + overflow: TextOverflow.ellipsis, + )), + IconButton( + icon: const Icon(Icons.close, + color: ColorsManager.warningRed, + size: 16), + onPressed: () { + context.read().add( + DeleteTag( + tagToDelete: tag, + tags: state.tags)); + }, + tooltip: 'Delete Tag', + ) + ], + ), + ), + DataCell( + Row( + children: [ + Expanded( + child: TextFormField( + controller: controller, + onChanged: (value) { + context + .read() + .add(UpdateTagEvent( + index: index, + tag: value.trim(), + )); + }, + decoration: const InputDecoration( + hintText: 'Enter Tag', + border: InputBorder.none, + ), + style: const TextStyle( + fontSize: 14, + color: ColorsManager.blackColor, + ), + ), + ), + SizedBox( + width: MediaQuery.of(context) + .size + .width * + 0.15, + child: PopupMenuButton( + color: ColorsManager.whiteColors, + icon: const Icon( + Icons.arrow_drop_down, + color: + ColorsManager.blackColor), + onSelected: (value) { + controller.text = value; + context + .read() + .add(UpdateTagEvent( + index: index, + tag: value, + )); + }, + itemBuilder: (context) { + return (allTags ?? []) + .where((tagValue) => !state + .tags + .map((e) => e.tag) + .contains(tagValue)) + .map((tagValue) { + return PopupMenuItem( + textStyle: const TextStyle( + color: ColorsManager + .textPrimaryColor), + value: tagValue, + child: ConstrainedBox( + constraints: + BoxConstraints( + minWidth: MediaQuery.of( + context) + .size + .width * + 0.15, + maxWidth: MediaQuery.of( + context) + .size + .width * + 0.15, + ), + child: Text( + tagValue, + overflow: TextOverflow + .ellipsis, + ), + )); + }).toList(); + }, + ), + ), + ], + ), + ), + DataCell( + DropdownButtonHideUnderline( + child: DropdownButton( + value: tag.location ?? 'None', + dropdownColor: ColorsManager + .whiteColors, // Dropdown background + style: const TextStyle( + color: Colors + .black), // Style for selected text + items: [ + const DropdownMenuItem( + value: 'None', + child: Text( + 'None', + style: TextStyle( + color: ColorsManager + .textPrimaryColor), + ), + ), + ...locations.map((location) { + return DropdownMenuItem( + value: location, + child: Text( + location, + style: const TextStyle( + color: ColorsManager + .textPrimaryColor), + ), + ); + }).toList(), + ], + onChanged: (value) { + if (value != null) { + context + .read() + .add(UpdateLocation( + index: index, + location: value, + )); + } + }, + ), + ), + ), + ], + ); + }), + ), + ), + if (state.errorMessage != null) + Text( + state.errorMessage!, + style: const TextStyle(color: ColorsManager.warningRed), + ), + ], + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + const SizedBox(width: 10), + Expanded( + child: CancelButton( + label: 'Cancel', + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + borderRadius: 10, + backgroundColor: state.isSaveEnabled + ? ColorsManager.secondaryColor + : ColorsManager.grayColor, + foregroundColor: ColorsManager.whiteColors, + onPressed: state.isSaveEnabled + ? () async { + Navigator.of(context).pop(); + final assignedTags = {}; + for (var tag in state.tags) { + if (tag.location == null || + subspaces == null) { + continue; + } + for (var subspace in subspaces!) { + if (tag.location == subspace.subspaceName) { + subspace.tags ??= []; + subspace.tags!.add(tag); + assignedTags.add(tag); + break; + } + } + } + onSave!(state.tags,subspaces); + } + : null, + child: const Text('Save'), + ), + ), + const SizedBox(width: 10), + ], + ), + ], + ); + } else if (state is AssignTagLoading) { + return const Center(child: CircularProgressIndicator()); + } else { + return const Center(child: Text('Something went wrong.')); + } + }, + ), + ); + } +} From a588351482cf5f7e8b0c7fdc057ecac478477aae Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 11:37:10 +0400 Subject: [PATCH 081/175] fixed space creation api --- .../views/add_device_type_widget.dart | 4 +- .../bloc/space_management_bloc.dart | 40 +++++++++++++++---- .../model/create_subspace_model.dart | 13 ++++++ .../all_spaces/model/space_model.dart | 2 +- .../all_spaces/model/tag.dart | 7 ++++ .../view/spaces_management_page.dart | 1 - .../widgets/community_structure_widget.dart | 30 +++++++++----- .../widgets/dialogs/create_space_dialog.dart | 24 +++++------ .../space_model/models/tag_body_model.dart | 16 ++++++++ lib/services/space_mana_api.dart | 31 +++++++++----- 10 files changed, 123 insertions(+), 45 deletions(-) create mode 100644 lib/pages/spaces_management/all_spaces/model/create_subspace_model.dart create mode 100644 lib/pages/spaces_management/space_model/models/tag_body_model.dart diff --git a/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart b/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart index 677b9fb9..9b9d6886 100644 --- a/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart +++ b/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart @@ -107,7 +107,9 @@ class AddDeviceTypeWidget extends StatelessWidget { spaceName: spaceName, initialTags: initialTags, title: dialogTitle, - onSave: onSave, + onSave: (tags,subspaces){ + onSave!(tags,subspaces); + }, ), ); } diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 554fdafe..6458bdf6 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -1,10 +1,14 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_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'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; import 'package:syncrow_web/services/product_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; @@ -343,7 +347,7 @@ class SpaceManagementBloc emit(SpaceCreationSuccess(spaces: updatedSpaces)); if (previousState is SpaceManagementLoaded) { - _updateLoadedState( + await _updateLoadedState( previousState, allSpaces, event.communityUuid, @@ -361,23 +365,25 @@ class SpaceManagementBloc } } - void _updateLoadedState( + Future _updateLoadedState( SpaceManagementLoaded previousState, List allSpaces, String communityUuid, Emitter emit, - ) { + ) async { + var prevSpaceModels = await fetchSpaceModels(previousState); + final communities = List.from(previousState.communities); for (var community in communities) { if (community.uuid == communityUuid) { community.spaces = allSpaces; emit(SpaceManagementLoaded( - communities: communities, - products: _cachedProducts ?? [], - selectedCommunity: community, - selectedSpace: null, - )); + communities: communities, + products: _cachedProducts ?? [], + selectedCommunity: community, + selectedSpace: null, + spaceModels: prevSpaceModels)); return; } } @@ -416,6 +422,21 @@ class SpaceManagementBloc ); } else { // Call create if the space does not have a UUID + final List tagBodyModels = space.tags != null + ? space.tags!.map((tag) => tag.toCreateTagBodyModel()).toList() + : []; + + final createSubspaceBodyModels = space.subspaces?.map((subspace) { + final tagBodyModels = subspace.tags + ?.map((tag) => tag.toCreateTagBodyModel()) + .toList() ?? + []; + return CreateSubspaceModel() + ..subspaceName = subspace.subspaceName + ..tags = tagBodyModels; + }).toList() ?? + []; + final response = await _api.createSpace( communityId: communityUuid, name: space.name, @@ -424,6 +445,9 @@ class SpaceManagementBloc position: space.position, icon: space.icon, direction: space.incomingConnection?.direction, + spaceModelUuid: space.spaceModel?.uuid, + tags: tagBodyModels, + subspaces: createSubspaceBodyModels, ); space.uuid = response?.uuid; } diff --git a/lib/pages/spaces_management/all_spaces/model/create_subspace_model.dart b/lib/pages/spaces_management/all_spaces/model/create_subspace_model.dart new file mode 100644 index 00000000..ce480169 --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/model/create_subspace_model.dart @@ -0,0 +1,13 @@ +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; + +class CreateSubspaceModel { + late String subspaceName; + late List? tags; + + Map toJson() { + return { + 'subspaceName': subspaceName, + 'tags': tags?.map((tag) => tag.toJson()).toList(), + }; + } +} diff --git a/lib/pages/spaces_management/all_spaces/model/space_model.dart b/lib/pages/spaces_management/all_spaces/model/space_model.dart index 64a98803..c9393cce 100644 --- a/lib/pages/spaces_management/all_spaces/model/space_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/space_model.dart @@ -24,7 +24,7 @@ class SpaceModel { SpaceStatus status; String internalId; SpaceTemplateModel? spaceModel; - final List? tags; + List? tags; List? subspaces; List outgoingConnections = []; // Connections from this space diff --git a/lib/pages/spaces_management/all_spaces/model/tag.dart b/lib/pages/spaces_management/all_spaces/model/tag.dart index b70865c7..98494f7f 100644 --- a/lib/pages/spaces_management/all_spaces/model/tag.dart +++ b/lib/pages/spaces_management/all_spaces/model/tag.dart @@ -1,5 +1,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; import 'package:uuid/uuid.dart'; class Tag { @@ -58,4 +59,10 @@ extension TagModelExtensions on Tag { ..tag = tag ?? '' ..productUuid = product?.uuid; } + + CreateTagBodyModel toCreateTagBodyModel() { + return CreateTagBodyModel() + ..tag = tag ?? '' + ..productUuid = product?.uuid; + } } diff --git a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart index 24b1b5cb..e601cca4 100644 --- a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart +++ b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart @@ -54,7 +54,6 @@ class SpaceManagementPageState extends State { shouldNavigateToSpaceModelPage: false, ); } else if (state is SpaceManagementLoaded) { - print("sdksndsnadf ${state.spaceModels}"); return LoadedSpaceView( communities: state.communities, selectedCommunity: state.selectedCommunity, diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index f48e2470..b7743a01 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -11,6 +11,8 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_pr import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/blank_community_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart'; @@ -55,7 +57,6 @@ class _CommunityStructureAreaState extends State { @override void initState() { super.initState(); - print("sxdsf ${widget.spaceModels}"); spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; @@ -293,21 +294,24 @@ class _CommunityStructureAreaState extends State { onCreateSpace: (String name, String icon, List selectedProducts, - SpaceTemplateModel? spaceModel) { + SpaceTemplateModel? spaceModel, + List? subspaces, + List? tags) { setState(() { // Set the first space in the center or use passed position Offset centerPosition = position ?? _getCenterPosition(screenSize); SpaceModel newSpace = SpaceModel( - name: name, - icon: icon, - position: centerPosition, - isPrivate: false, - children: [], - status: SpaceStatus.newSpace, - spaceModel: spaceModel, - ); + name: name, + icon: icon, + position: centerPosition, + isPrivate: false, + children: [], + status: SpaceStatus.newSpace, + spaceModel: spaceModel, + subspaces: subspaces, + tags: tags); if (parentIndex != null && direction != null) { SpaceModel parentSpace = spaces[parentIndex]; @@ -346,12 +350,16 @@ class _CommunityStructureAreaState extends State { onCreateSpace: (String name, String icon, List selectedProducts, - SpaceTemplateModel? spaceModel) { + SpaceTemplateModel? spaceModel, + List? subspaces, + List? tags) { setState(() { // Update the space's properties space.name = name; space.icon = icon; space.spaceModel = spaceModel; + space.subspaces= subspaces; + space.tags = tags; if (space.status != SpaceStatus.newSpace) { space.status = SpaceStatus.modified; // Mark as modified diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index ed4a22b9..6f3e9fcb 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -18,7 +18,7 @@ import 'package:syncrow_web/utils/constants/space_icon_const.dart'; class CreateSpaceDialog extends StatefulWidget { final Function(String, String, List selectedProducts, - SpaceTemplateModel? spaceModel) onCreateSpace; + SpaceTemplateModel? spaceModel, List? subspaces, List? tags) onCreateSpace; final List? products; final String? name; final String? icon; @@ -487,7 +487,7 @@ class CreateSpaceDialogState extends State { GestureDetector( onTap: () async { _showTagCreateDialog(context, enteredName, - tags, subspaces, widget.products); + widget.products); // Edit action }, child: Chip( @@ -511,8 +511,8 @@ class CreateSpaceDialogState extends State { ) : DefaultButton( onPressed: () { - _showTagCreateDialog(context, enteredName, tags, - subspaces, widget.products); + _showTagCreateDialog( + context, enteredName, widget.products); }, backgroundColor: ColorsManager.textFieldGreyColor, foregroundColor: Colors.black, @@ -580,7 +580,7 @@ class CreateSpaceDialogState extends State { : (widget.name ?? ''); if (newName.isNotEmpty) { widget.onCreateSpace(newName, selectedIcon, - selectedProducts, selectedSpaceModel); + selectedProducts, selectedSpaceModel,subspaces,tags); Navigator.of(context).pop(); } } @@ -673,11 +673,7 @@ class CreateSpaceDialogState extends State { } void _showTagCreateDialog( - BuildContext context, - String name, - final List? spaceTags, - List? subspaces, - List? products) { + BuildContext context, String name, List? products) { showDialog( context: context, builder: (BuildContext context) { @@ -685,16 +681,18 @@ class CreateSpaceDialogState extends State { spaceName: name, products: products, subspaces: subspaces, - spaceTags: spaceTags, + spaceTags: tags, allTags: [], initialSelectedProducts: createInitialSelectedProducts(tags, subspaces), onSave: (selectedSpaceTags, selectedSubspaces) { setState(() { - if (spaceTags != null) selectedSpaceTags = spaceTags; + tags = selectedSpaceTags; + selectedSpaceModel = null; + if (selectedSubspaces != null) { if (subspaces != null) { - for (final subspace in subspaces) { + for (final subspace in subspaces!) { for (final selectedSubspace in selectedSubspaces) { if (subspace.subspaceName == selectedSubspace.subspaceName) { diff --git a/lib/pages/spaces_management/space_model/models/tag_body_model.dart b/lib/pages/spaces_management/space_model/models/tag_body_model.dart new file mode 100644 index 00000000..d66e2884 --- /dev/null +++ b/lib/pages/spaces_management/space_model/models/tag_body_model.dart @@ -0,0 +1,16 @@ +class CreateTagBodyModel { + late String tag; + late final String? productUuid; + + Map toJson() { + return { + 'tag': tag, + 'productUuid': productUuid, + }; + } + + @override + String toString() { + return toJson().toString(); + } +} diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index b637cbc7..2a2d42ad 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_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'; @@ -161,15 +164,17 @@ class CommunitySpaceManagementApi { } } - Future createSpace({ - required String communityId, - required String name, - String? parentId, - String? direction, - bool isPrivate = false, - required Offset position, - String? icon, - }) async { + Future createSpace( + {required String communityId, + required String name, + String? parentId, + String? direction, + bool isPrivate = false, + required Offset position, + String? spaceModelUuid, + String? icon, + List? tags, + List? subspaces}) async { try { final body = { 'spaceName': name, @@ -182,6 +187,13 @@ class CommunitySpaceManagementApi { if (parentId != null) { body['parentUuid'] = parentId; } + if (tags != null) { + body['tags'] = tags; + } + + if (spaceModelUuid != null) body['spaceModelUuid'] = spaceModelUuid; + + if (subspaces != null) body['subspaces'] = subspaces; final response = await HTTPService().post( path: ApiEndpoints.createSpace .replaceAll('{communityId}', communityId) @@ -263,7 +275,6 @@ class CommunitySpaceManagementApi { .replaceAll('{communityId}', communityId) .replaceAll('{projectId}', TempConst.projectId), expectedResponseModel: (json) { - print(json); final spaceModels = (json['data'] as List) .map((spaceJson) => SpaceModel.fromJson(spaceJson)) .toList(); From a381fd317dde92024a9b1c88d39a5ae288d120c4 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 12:10:54 +0400 Subject: [PATCH 082/175] added space delete --- .../all_spaces/model/space_model.dart | 2 +- .../community_structure_header_widget.dart | 27 ++++++++++++------- .../widgets/community_structure_widget.dart | 16 +++++++---- lib/services/api/http_service.dart | 13 ++++++++- lib/utils/constants/temp_const.dart | 2 +- 5 files changed, 43 insertions(+), 17 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/model/space_model.dart b/lib/pages/spaces_management/all_spaces/model/space_model.dart index c9393cce..c8da9d9e 100644 --- a/lib/pages/spaces_management/all_spaces/model/space_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/space_model.dart @@ -7,7 +7,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/space_tem import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:uuid/uuid.dart'; -enum SpaceStatus { newSpace, modified, unchanged, deleted } +enum SpaceStatus { newSpace, modified, unchanged, deleted, parentDeleted } class SpaceModel { String? uuid; diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart index 612f9101..02d3819a 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.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/pages/spaces_management/create_community/view/create_community_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -17,6 +18,7 @@ class CommunityStructureHeader extends StatefulWidget { final ValueChanged onNameSubmitted; final List communities; final CommunityModel? community; + final SpaceModel? selectedSpace; const CommunityStructureHeader( {super.key, @@ -29,7 +31,8 @@ class CommunityStructureHeader extends StatefulWidget { required this.onEditName, required this.onNameSubmitted, this.community, - required this.communities}); + required this.communities, + this.selectedSpace}); @override State createState() => @@ -137,10 +140,8 @@ class _CommunityStructureHeaderState extends State { ], ), ), - if (widget.isSave) ...[ - const SizedBox(width: 8), - _buildActionButtons(theme), - ], + const SizedBox(width: 8), + _buildActionButtons(theme), ], ), ], @@ -152,11 +153,19 @@ class _CommunityStructureHeaderState extends State { alignment: WrapAlignment.end, spacing: 10, children: [ + if (widget.isSave) + _buildButton( + label: "Save", + icon: const Icon(Icons.save, + size: 18, color: ColorsManager.spaceColor), + onPressed: widget.onSave, + theme: theme), + if(widget.selectedSpace!= null) _buildButton( - label: "Save", - icon: const Icon(Icons.save, - size: 18, color: ColorsManager.spaceColor), - onPressed: widget.onSave, + label: "Delete", + icon: const Icon(Icons.delete, + size: 18, color: ColorsManager.warningRed), + onPressed: widget.onDelete, theme: theme), ], ); diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index b7743a01..05a80780 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -131,6 +131,7 @@ class _CommunityStructureAreaState extends State { isEditingName: isEditingName, nameController: _nameController, onSave: _saveSpaces, + selectedSpace: widget.selectedSpace, onDelete: _onDelete, onEditName: () { setState(() { @@ -176,7 +177,8 @@ class _CommunityStructureAreaState extends State { painter: CurvedLinePainter([connection])), ), for (var entry in spaces.asMap().entries) - if (entry.value.status != SpaceStatus.deleted) + if (entry.value.status != SpaceStatus.deleted || + entry.value.status != SpaceStatus.parentDeleted) Positioned( left: entry.value.position.dx, top: entry.value.position.dy, @@ -358,7 +360,7 @@ class _CommunityStructureAreaState extends State { space.name = name; space.icon = icon; space.spaceModel = spaceModel; - space.subspaces= subspaces; + space.subspaces = subspaces; space.tags = tags; if (space.status != SpaceStatus.newSpace) { @@ -382,7 +384,8 @@ class _CommunityStructureAreaState extends State { List result = []; void flatten(SpaceModel space) { - if (space.status == SpaceStatus.deleted) return; + if (space.status == SpaceStatus.deleted || + space.status == SpaceStatus.parentDeleted) return; result.add(space); @@ -475,7 +478,8 @@ class _CommunityStructureAreaState extends State { void _markChildrenAsDeleted(SpaceModel parent) { for (var child in parent.children) { - child.status = SpaceStatus.deleted; + child.status = SpaceStatus.parentDeleted; + _markChildrenAsDeleted(child); } } @@ -483,7 +487,9 @@ class _CommunityStructureAreaState extends State { void _removeConnectionsForDeletedSpaces() { connections.removeWhere((connection) { return connection.startSpace.status == SpaceStatus.deleted || - connection.endSpace.status == SpaceStatus.deleted; + connection.endSpace.status == SpaceStatus.deleted || + connection.startSpace.status == SpaceStatus.parentDeleted || + connection.endSpace.status == SpaceStatus.parentDeleted; }); } diff --git a/lib/services/api/http_service.dart b/lib/services/api/http_service.dart index b75f05cf..324f1938 100644 --- a/lib/services/api/http_service.dart +++ b/lib/services/api/http_service.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:dio/dio.dart'; import 'package:syncrow_web/services/api/http_interceptor.dart'; import 'package:syncrow_web/services/locator.dart'; @@ -121,13 +123,22 @@ class HTTPService { required T Function(dynamic) expectedResponseModel, bool showServerMessage = true, }) async { + log('DELETE Request Initiated', name: 'API DELETE'); + log('Path: $path', name: 'API DELETE'); + if (queryParameters != null) { + log('Query Parameters: $queryParameters', name: 'API DELETE'); + } + try { final response = await client.delete( path, queryParameters: queryParameters, ); + log('Response: ${response.data}', name: 'API DELETE'); return expectedResponseModel(response.data); - } catch (error) { + } catch (error, stackTrace) { + log('Error during DELETE Request: $error', + name: 'API DELETE', error: error, stackTrace: stackTrace); rethrow; } } diff --git a/lib/utils/constants/temp_const.dart b/lib/utils/constants/temp_const.dart index e5847b98..bcd1a1d6 100644 --- a/lib/utils/constants/temp_const.dart +++ b/lib/utils/constants/temp_const.dart @@ -1,3 +1,3 @@ class TempConst { - static const projectId = '0e62577c-06fa-41b9-8a92-99a21fbaf51c'; + static const projectId = '0685c781-df33-4cbf-bf65-9f4e835eb468'; } From 2abe7a6feb6e17d39ec9976337730d84a8df29c6 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 12:11:32 +0400 Subject: [PATCH 083/175] revert --- lib/services/api/http_service.dart | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/lib/services/api/http_service.dart b/lib/services/api/http_service.dart index 324f1938..b75f05cf 100644 --- a/lib/services/api/http_service.dart +++ b/lib/services/api/http_service.dart @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:dio/dio.dart'; import 'package:syncrow_web/services/api/http_interceptor.dart'; import 'package:syncrow_web/services/locator.dart'; @@ -123,22 +121,13 @@ class HTTPService { required T Function(dynamic) expectedResponseModel, bool showServerMessage = true, }) async { - log('DELETE Request Initiated', name: 'API DELETE'); - log('Path: $path', name: 'API DELETE'); - if (queryParameters != null) { - log('Query Parameters: $queryParameters', name: 'API DELETE'); - } - try { final response = await client.delete( path, queryParameters: queryParameters, ); - log('Response: ${response.data}', name: 'API DELETE'); return expectedResponseModel(response.data); - } catch (error, stackTrace) { - log('Error during DELETE Request: $error', - name: 'API DELETE', error: error, stackTrace: stackTrace); + } catch (error) { rethrow; } } From 408c40aa60ef1d9e599c85e940fc7690f98a558b Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 12:12:14 +0400 Subject: [PATCH 084/175] revert --- .env.development | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.development b/.env.development index e77609dc..1fd358ec 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,2 @@ ENV_NAME=development -BASE_URL=https://syncrow-dev.azurewebsites.net \ No newline at end of file +BASE_URL=http://localhost:4001 \ No newline at end of file From 15023e5882cafec4e2380f6ec1f2712bd5ef747d Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 12 Jan 2025 15:32:03 +0300 Subject: [PATCH 085/175] fixes filter and table view and add user dialog --- .../model/roles_user_model.dart | 2 +- .../add_user_dialog/view/add_user_dialog.dart | 6 +- .../view/delete_user_dialog.dart | 55 ++- .../view/popup_menu_filter.dart | 255 +++++----- .../add_user_dialog/view/role_dropdown.dart | 6 +- .../users_table/bloc/user_table_bloc.dart | 70 ++- .../users_table/bloc/user_table_event.dart | 33 +- .../users_table/view/user_table.dart | 436 +++++++----------- .../users_table/view/users_page.dart | 108 ++--- lib/services/user_permission.dart | 13 +- 10 files changed, 496 insertions(+), 488 deletions(-) diff --git a/lib/pages/roles_and_permission/model/roles_user_model.dart b/lib/pages/roles_and_permission/model/roles_user_model.dart index 6298dbe6..44e3f493 100644 --- a/lib/pages/roles_and_permission/model/roles_user_model.dart +++ b/lib/pages/roles_and_permission/model/roles_user_model.dart @@ -42,7 +42,7 @@ class RolesUserModel { invitedBy: json['invitedBy'].toString().toLowerCase().replaceAll("_", " "), phoneNumber: json['phoneNumber'], - jobTitle: json['jobTitle'].toString(), + jobTitle: json['jobTitle'] ?? "-", createdDate: json['createdDate'], createdTime: json['createdTime'], ); diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart index 48df801a..f29aec8e 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart @@ -114,7 +114,7 @@ class _AddNewUserDialogState extends State { currentStep++; if (currentStep == 2) { _blocRole.add( - CheckStepStatus(isEditUser: false)); + const CheckStepStatus(isEditUser: false)); } else if (currentStep == 3) { _blocRole .add(const CheckSpacesStepStatus()); @@ -151,7 +151,7 @@ class _AddNewUserDialogState extends State { Widget _getFormContent() { switch (currentStep) { case 1: - return BasicsView( + return const BasicsView( userId: '', ); case 2: @@ -172,7 +172,7 @@ class _AddNewUserDialogState extends State { bloc.add(const CheckSpacesStepStatus()); currentStep = step; Future.delayed(const Duration(milliseconds: 500), () { - bloc.add(ValidateBasicsStep()); + bloc.add(const ValidateBasicsStep()); }); }); diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart index 837ee62c..10e8c273 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart @@ -11,7 +11,14 @@ class DeleteUserDialog extends StatefulWidget { } class _DeleteUserDialogState extends State { - int currentStep = 1; + bool isLoading = false; + bool _isDisposed = false; + + @override + void dispose() { + _isDisposed = true; + super.dispose(); + } @override Widget build(BuildContext context) { @@ -56,7 +63,7 @@ class _DeleteUserDialogState extends State { Expanded( child: InkWell( onTap: () { - Navigator.of(context).pop(true); + Navigator.of(context).pop(false); // Return false if canceled }, child: Container( padding: const EdgeInsets.all(10), @@ -76,7 +83,26 @@ class _DeleteUserDialogState extends State { )), Expanded( child: InkWell( - onTap: widget.onTapDelete, + onTap: isLoading + ? null + : () async { + setState(() { + isLoading = true; + }); + + try { + if (widget.onTapDelete != null) { + await widget.onTapDelete!(); + } + } finally { + if (!_isDisposed) { + setState(() { + isLoading = false; + }); + } + } + Navigator.of(context).pop(true); + }, child: Container( padding: const EdgeInsets.all(10), decoration: const BoxDecoration( @@ -91,13 +117,22 @@ class _DeleteUserDialogState extends State { ), ), ), - child: const Center( - child: Text( - 'Delete', - style: TextStyle( - color: ColorsManager.red, - ), - ))), + child: Center( + child: isLoading + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + color: ColorsManager.red, + strokeWidth: 2.0, + ), + ) + : const Text( + 'Delete', + style: TextStyle( + color: ColorsManager.red, + ), + ))), )), ], ) diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart index 7c3c1ba5..c3a245c1 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart @@ -5,141 +5,156 @@ import 'package:syncrow_web/utils/style.dart'; Future showPopUpFilterMenu({ required BuildContext context, - Function()? onSortAtoZ, - Function()? onSortZtoA, + required Function(String value)? onSortAtoZ, // Accept a parameter + required Function(String value)? onSortZtoA, // Accept a parameter Function()? cancelButton, required Map checkboxStates, required RelativeRect position, Function()? onOkPressed, List? list, + String? isSelected, }) async { - - await showMenu( context: context, - position:position, - - + position: position, color: ColorsManager.whiteColors, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(10)), ), items: [ PopupMenuItem( - onTap: onSortAtoZ, - child: ListTile( - leading: Image.asset( - Assets.AtoZIcon, - width: 25, - ), - title: const Text( - "Sort A to Z", - style: TextStyle(color: Colors.blueGrey), - ), - ), - ), - PopupMenuItem( - onTap: onSortZtoA, - child: ListTile( - leading: Image.asset( - Assets.ZtoAIcon, - width: 25, - ), - title: const Text( - "Sort Z to A", - style: TextStyle(color: Colors.blueGrey), - ), - ), - ), - const PopupMenuDivider(), - const PopupMenuItem( - child: Text( - "Filter by Status", - style: TextStyle(fontWeight: FontWeight.bold), - ) - // Container( - // decoration: containerDecoration.copyWith( - // boxShadow: [], - // borderRadius: const BorderRadius.only( - // topLeft: Radius.circular(10), topRight: Radius.circular(10))), - // child: Padding( - // padding: const EdgeInsets.all(8.0), - // child: TextFormField( - // onChanged: onTextFieldChanged, - // style: const TextStyle(color: Colors.black), - // decoration: textBoxDecoration(radios: 15)!.copyWith( - // fillColor: ColorsManager.whiteColors, - // errorStyle: const TextStyle(height: 0), - // hintStyle: context.textTheme.titleSmall?.copyWith( - // color: Colors.grey, - // fontSize: 12, - // ), - // hintText: 'Search', - // suffixIcon: SizedBox( - // child: SvgPicture.asset( - // Assets.searchIconUser, - // fit: BoxFit.none, - // ), - // ), - // ), - // // "Filter by Status", - // // style: TextStyle(fontWeight: FontWeight.bold), - // ), - // ), - // ), - ), - PopupMenuItem( - child: Container( - decoration: containerDecoration.copyWith( - boxShadow: [], - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(10), - bottomRight: Radius.circular(10))), - padding: const EdgeInsets.all(10), - height: 200, - width: 400, - child: Container( - padding: const EdgeInsets.all(10), - color: Colors.white, - child: ListView.builder( - itemCount: list?.length ?? 0, - itemBuilder: (context, index) { - final item = list![index]; - return CheckboxListTile( - dense: true, - title: Text(item), - value: checkboxStates[item], - onChanged: (bool? newValue) { - checkboxStates[item] = newValue ?? false; - (context as Element).markNeedsBuild(); - }, - ); - }, - ), - ), - ), - ), - PopupMenuItem( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () { - Navigator.of(context).pop(); // Close the menu - }, - child: const Text("Cancel"), - ), - GestureDetector( - onTap: onOkPressed, - child: const Text( - "OK", - style: TextStyle( - color: ColorsManager.spaceColor, + enabled: false, + child: StatefulBuilder( + builder: (context, setState) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + child: ListTile( + onTap: () { + setState(() { + if (isSelected == 'Asc') { + isSelected = null; + onSortAtoZ?.call(''); + } else { + onSortAtoZ?.call('Asc'); + isSelected = 'Asc'; + } + }); + }, + leading: Image.asset( + Assets.AtoZIcon, + width: 25, + ), + title: Text( + "Sort A to Z", + style: TextStyle( + color: isSelected == "Asc" + ? ColorsManager.blackColor + : ColorsManager.grayColor), + ), + ), ), - ), - ), - ], + ListTile( + onTap: () { + setState(() { + if (isSelected == 'Desc') { + isSelected = null; + onSortZtoA?.call(''); + } else { + onSortZtoA?.call('Desc'); + isSelected = 'Desc'; + } + }); + }, + leading: Image.asset( + Assets.ZtoAIcon, + width: 25, + ), + title: Text( + "Sort Z to A", + style: TextStyle( + color: isSelected == "Desc" + ? ColorsManager.blackColor + : ColorsManager.grayColor), + ), + ), + const Divider(), + const Text( + "Filter by Status", + style: TextStyle(fontWeight: FontWeight.bold), + ), + Container( + decoration: containerDecoration.copyWith( + boxShadow: [], + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(10), + topRight: Radius.circular(10), + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10))), + padding: const EdgeInsets.all(10), + height: 200, + width: 400, + child: Container( + padding: const EdgeInsets.all(10), + color: Colors.white, + child: ListView.builder( + itemCount: list?.length ?? 0, + itemBuilder: (context, index) { + final item = list![index]; + return Row( + children: [ + Checkbox( + value: checkboxStates[item], + onChanged: (bool? newValue) { + checkboxStates[item] = newValue ?? false; + (context as Element).markNeedsBuild(); + }, + ), + Text( + item, + style: TextStyle(color: ColorsManager.grayColor), + ), + ], + ); + }, + ), + ), + ), + const SizedBox( + height: 10, + ), + const Divider(), + const SizedBox( + height: 10, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); // Close the menu + }, + child: const Text("Cancel"), + ), + GestureDetector( + onTap: onOkPressed, + child: const Text( + "OK", + style: TextStyle( + color: ColorsManager.spaceColor, + ), + ), + ), + ], + ), + const SizedBox( + height: 10, + ), + ], + ); + }, ), ), ], diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart index 1bc9331e..c8126dbd 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart @@ -52,14 +52,16 @@ class _RoleDropdownState extends State { SizedBox( child: DropdownButtonFormField( dropdownColor: ColorsManager.whiteColors, - alignment: Alignment.center, + // alignment: Alignment., focusColor: Colors.white, autofocus: true, value: selectedRole.isNotEmpty ? selectedRole : null, items: widget.bloc!.roles.map((role) { return DropdownMenuItem( value: role.uuid, - child: Text(role.type), + child: Text( + ' ${role.type[0].toUpperCase()}${role.type.substring(1)}', + ), ); }).toList(), onChanged: (value) { diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart index aa014bd5..b1131ca4 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart @@ -133,7 +133,10 @@ class UserTableBloc extends Bloc { } else { emit(UsersLoadingState()); currentSortOrder = "Asc"; - users.sort((a, b) => a.firstName!.compareTo(b.firstName!)); + users.sort((a, b) => a.firstName + .toString() + .toLowerCase() + .compareTo(b.firstName.toString().toLowerCase())); emit(UsersLoadedState(users: users)); } } @@ -164,6 +167,7 @@ class UserTableBloc extends Bloc { emit(UsersLoadedState(users: users)); } else { emit(UsersLoadingState()); + currentSortOrder = "NewestToOldest"; users.sort((a, b) { final dateA = _parseDateTime(a.createdDate); final dateB = _parseDateTime(b.createdDate); @@ -188,6 +192,7 @@ class UserTableBloc extends Bloc { final dateB = _parseDateTime(b.createdDate); return dateA.compareTo(dateB); }); + currentSortOrder = "OldestToNewest"; emit(UsersLoadedState(users: users)); } } @@ -210,7 +215,7 @@ class UserTableBloc extends Bloc { final query = event.query.toLowerCase(); final filteredUsers = initialUsers.where((user) { final fullName = "${user.firstName} ${user.lastName}".toLowerCase(); - final email = user.email.toLowerCase() ; + final email = user.email.toLowerCase(); return fullName.contains(query) || email.contains(query); }).toList(); emit(UsersLoadedState(users: filteredUsers)); @@ -256,49 +261,96 @@ class UserTableBloc extends Bloc { void _filterUsersByRole( FilterUsersByRoleEvent event, Emitter emit) { - selectedRoles = event.selectedRoles.toSet(); + selectedRoles = event.selectedRoles!.toSet(); final filteredUsers = initialUsers.where((user) { if (selectedRoles.isEmpty) return true; return selectedRoles.contains(user.roleType); }).toList(); + if (event.sortOrder == "Asc") { + currentSortOrder = "Asc"; + filteredUsers.sort((a, b) => a.firstName + .toString() + .toLowerCase() + .compareTo(b.firstName.toString().toLowerCase())); + } else if (event.sortOrder == "Desc") { + currentSortOrder = "Desc"; + filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); + } else { + currentSortOrder = ""; + } + emit(UsersLoadedState(users: filteredUsers)); } void _filterUsersByJobTitle( FilterUsersByJobEvent event, Emitter emit) { - selectedJobTitles = event.selectedJob.toSet(); - + selectedJobTitles = event.selectedJob!.toSet(); + emit(UsersLoadingState()); final filteredUsers = initialUsers.where((user) { if (selectedJobTitles.isEmpty) return true; return selectedJobTitles.contains(user.jobTitle); }).toList(); - + if (event.sortOrder == "Asc") { + currentSortOrder = "Asc"; + filteredUsers.sort((a, b) => a.firstName + .toString() + .toLowerCase() + .compareTo(b.firstName.toString().toLowerCase())); + } else if (event.sortOrder == "Desc") { + currentSortOrder = "Desc"; + filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); + } else { + currentSortOrder = ""; + } emit(UsersLoadedState(users: filteredUsers)); } void _filterUsersByCreated( FilterUsersByCreatedEvent event, Emitter emit) { - selectedCreatedBy = event.selectedCreatedBy.toSet(); + selectedCreatedBy = event.selectedCreatedBy!.toSet(); final filteredUsers = initialUsers.where((user) { if (selectedCreatedBy.isEmpty) return true; return selectedCreatedBy.contains(user.invitedBy); }).toList(); + if (event.sortOrder == "Asc") { + currentSortOrder = "Asc"; + filteredUsers.sort((a, b) => a.firstName + .toString() + .toLowerCase() + .compareTo(b.firstName.toString().toLowerCase())); + } else if (event.sortOrder == "Desc") { + currentSortOrder = "Desc"; + filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); + } else { + currentSortOrder = ""; + } emit(UsersLoadedState(users: filteredUsers)); } void _filterUserStatus( FilterUsersByDeActevateEvent event, Emitter emit) { - selectedStatuses = event.selectedActivate.toSet(); + selectedStatuses = event.selectedActivate!.toSet(); final filteredUsers = initialUsers.where((user) { if (selectedStatuses.isEmpty) return true; return selectedStatuses.contains(user.status); }).toList(); - + if (event.sortOrder == "Asc") { + currentSortOrder = "Asc"; + filteredUsers.sort((a, b) => a.firstName + .toString() + .toLowerCase() + .compareTo(b.firstName.toString().toLowerCase())); + } else if (event.sortOrder == "Desc") { + currentSortOrder = "Desc"; + filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); + } else { + currentSortOrder = ""; + } emit(UsersLoadedState(users: filteredUsers)); } diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart index a81002ad..1d9567cf 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart @@ -89,35 +89,36 @@ class DeleteUserEvent extends UserTableEvent { } class FilterUsersByRoleEvent extends UserTableEvent { - final List selectedRoles; + final List? selectedRoles; + final String? sortOrder; - FilterUsersByRoleEvent(this.selectedRoles); - @override - List get props => [selectedRoles]; + const FilterUsersByRoleEvent({this.selectedRoles, this.sortOrder}); + List get props => [selectedRoles, sortOrder]; } class FilterUsersByJobEvent extends UserTableEvent { - final List selectedJob; + final List? selectedJob; + final String? sortOrder; - FilterUsersByJobEvent(this.selectedJob); - @override - List get props => [selectedJob]; + const FilterUsersByJobEvent({this.selectedJob, this.sortOrder}); + List get props => [selectedJob, sortOrder]; } class FilterUsersByCreatedEvent extends UserTableEvent { - final List selectedCreatedBy; + final List? selectedCreatedBy; - FilterUsersByCreatedEvent(this.selectedCreatedBy); - @override - List get props => [selectedCreatedBy]; + final String? sortOrder; + + const FilterUsersByCreatedEvent({this.selectedCreatedBy, this.sortOrder}); + List get props => [selectedCreatedBy, sortOrder]; } class FilterUsersByDeActevateEvent extends UserTableEvent { - final List selectedActivate; + final List? selectedActivate; + final String? sortOrder; - FilterUsersByDeActevateEvent(this.selectedActivate); - @override - List get props => [selectedActivate]; + const FilterUsersByDeActevateEvent({this.selectedActivate, this.sortOrder}); + List get props => [selectedActivate, sortOrder]; } class FilterOptionsEvent extends UserTableEvent { diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart index 6662a9e2..ca641cbe 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart @@ -19,19 +19,13 @@ class DynamicTableScreen extends StatefulWidget { class _DynamicTableScreenState extends State with WidgetsBindingObserver { late List columnWidths; + late double totalWidth; - // @override - // void initState() { - // super.initState(); - // // Initialize column widths with default sizes proportional to the screen width - // // Assigning placeholder values here. The actual sizes will be updated in `build`. - // } @override void initState() { super.initState(); - setState(() { - columnWidths = List.filled(widget.titles.length, 150.0); - }); + columnWidths = List.filled(widget.titles.length, 150.0); + totalWidth = columnWidths.reduce((a, b) => a + b); WidgetsBinding.instance.addObserver(this); } @@ -44,7 +38,6 @@ class _DynamicTableScreenState extends State @override void didChangeMetrics() { super.didChangeMetrics(); - // Screen size might have changed final newScreenWidth = MediaQuery.of(context).size.width; setState(() { columnWidths = List.generate(widget.titles.length, (index) { @@ -64,8 +57,6 @@ class _DynamicTableScreenState extends State @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; - - // Initialize column widths if they are still set to placeholder values if (columnWidths.every((width) => width == 120.0)) { columnWidths = List.generate(widget.titles.length, (index) { if (index == 1) { @@ -77,280 +68,193 @@ class _DynamicTableScreenState extends State }); setState(() {}); } - return Container( - child: SingleChildScrollView( - clipBehavior: Clip.none, - scrollDirection: Axis.horizontal, - child: Container( - decoration: containerDecoration.copyWith( - color: ColorsManager.whiteColors, - borderRadius: const BorderRadius.all(Radius.circular(20))), - child: FittedBox( - child: Column( - children: [ - // Header Row with Resizable Columns - Container( - width: MediaQuery.of(context).size.width, - decoration: containerDecoration.copyWith( - color: ColorsManager.circleRolesBackground, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(15), - topRight: Radius.circular(15))), - child: Row( - children: List.generate(widget.titles.length, (index) { - return Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - FittedBox( - child: Container( - padding: const EdgeInsets.only(left: 5, right: 5), - width: columnWidths[index], - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - child: Text( - widget.titles[index], - maxLines: 2, - style: const TextStyle( - overflow: TextOverflow.ellipsis, - fontWeight: FontWeight.w400, - fontSize: 13, - color: ColorsManager.grayColor, - ), + return SingleChildScrollView( + clipBehavior: Clip.none, + scrollDirection: Axis.horizontal, + child: Container( + decoration: containerDecoration.copyWith( + color: ColorsManager.whiteColors, + borderRadius: const BorderRadius.all(Radius.circular(20))), + child: FittedBox( + child: Column( + children: [ + Container( + width: totalWidth, + decoration: containerDecoration.copyWith( + color: ColorsManager.circleRolesBackground, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(15), + topRight: Radius.circular(15))), + child: Row( + children: List.generate(widget.titles.length, (index) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + FittedBox( + child: Container( + padding: const EdgeInsets.only(left: 5, right: 5), + width: columnWidths[index], + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + child: Text( + widget.titles[index], + maxLines: 2, + style: const TextStyle( + overflow: TextOverflow.ellipsis, + fontWeight: FontWeight.w400, + fontSize: 13, + color: ColorsManager.grayColor, ), ), - if (index != 1 && - index != 9 && - index != 8 && - index != 5) - FittedBox( - child: IconButton( - icon: SvgPicture.asset( - Assets.filterTableIcon, - fit: BoxFit.none, - ), - onPressed: () { - if (widget.onFilter != null) { - widget.onFilter!(index); - } - }, + ), + if (index != 1 && + index != 9 && + index != 8 && + index != 5) + FittedBox( + child: IconButton( + icon: SvgPicture.asset( + Assets.filterTableIcon, + fit: BoxFit.none, ), - ) - ], - ), - ), - ), - GestureDetector( - onHorizontalDragUpdate: (details) { - setState(() { - columnWidths[index] = (columnWidths[index] + - details.delta.dx) - .clamp( - 150.0, 300.0); // Minimum & Maximum size - }); - }, - child: MouseRegion( - cursor: SystemMouseCursors - .resizeColumn, // Set the cursor to resize - child: Container( - color: Colors.green, - child: Container( - color: ColorsManager.boxDivider, - width: 1, - height: 50, // Height of the header cell - ), - ), - ), - ), - ], - ); - }), - ), - ), - // Data Rows with Dividers - widget.rows.isEmpty - ? Container( - child: SizedBox( - height: MediaQuery.of(context).size.height / 2, - child: Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Column( - children: [ - SvgPicture.asset(Assets.emptyTable), - const SizedBox( - height: 15, + onPressed: () { + if (widget.onFilter != null) { + widget.onFilter!(index); + } + }, ), - const Text( - 'No Users', - style: TextStyle( - color: ColorsManager.lightGrayColor, - fontSize: 16, - fontWeight: FontWeight.w700), - ) - ], - ), + ) ], ), ), ), - ) - : Center( - child: Container( - // height: MediaQuery.of(context).size.height * 0.59, - width: MediaQuery.of(context).size.width, - decoration: containerDecoration.copyWith( - color: ColorsManager.whiteColors, - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(15), - bottomRight: Radius.circular(15))), - child: ListView.builder( - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: widget.rows.length, - itemBuilder: (context, rowIndex) { - if (columnWidths - .every((width) => width == 120.0)) { - columnWidths = List.generate( - widget.titles.length, (index) { - if (index == 1) { - return screenWidth * 0.11; - } else if (index == 9) { - return screenWidth * 0.2; - } + GestureDetector( + onHorizontalDragUpdate: (details) { + setState(() { + columnWidths[index] = + (columnWidths[index] + details.delta.dx) + .clamp(150.0, 300.0); + totalWidth = columnWidths.reduce((a, b) => a + b); + }); + }, + child: MouseRegion( + cursor: SystemMouseCursors.resizeColumn, + child: Container( + color: Colors.green, + child: Container( + color: ColorsManager.boxDivider, + width: 1, + height: 50, + ), + ), + ), + ), + ], + ); + }), + ), + ), + widget.rows.isEmpty + ? SizedBox( + height: MediaQuery.of(context).size.height / 2, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + SvgPicture.asset(Assets.emptyTable), + const SizedBox( + height: 15, + ), + const Text( + 'No Users', + style: TextStyle( + color: ColorsManager.lightGrayColor, + fontSize: 16, + fontWeight: FontWeight.w700), + ) + ], + ), + ], + ), + ) + : Center( + child: Container( + width: totalWidth, + decoration: containerDecoration.copyWith( + color: ColorsManager.whiteColors, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(15), + bottomRight: Radius.circular(15))), + child: ListView.builder( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: widget.rows.length, + itemBuilder: (context, rowIndex) { + if (columnWidths.every((width) => width == 120.0)) { + columnWidths = List.generate( + widget.titles.length, (index) { + if (index == 1) { return screenWidth * 0.11; - }); - setState(() {}); - } - final row = widget.rows[rowIndex]; - return Column( - children: [ - Container( - child: Padding( - padding: const EdgeInsets.only( - left: 5, - top: 10, - right: 5, - bottom: 10), - child: Row( - children: - List.generate(row.length, (index) { - return SizedBox( - width: columnWidths[index], - child: SizedBox( - child: Padding( - padding: const EdgeInsets.only( - left: 15, right: 10), - child: row[index], - ), - ), - ); - }), - ), - ), - ), - if (rowIndex < widget.rows.length - 1) - Row( - children: List.generate( - widget.titles.length, (index) { + } else if (index == 9) { + return screenWidth * 0.2; + } + return screenWidth * 0.11; + }); + setState(() {}); + } + final row = widget.rows[rowIndex]; + return Column( + children: [ + Container( + child: Padding( + padding: const EdgeInsets.only( + left: 5, top: 10, right: 5, bottom: 10), + child: Row( + children: + List.generate(row.length, (index) { return SizedBox( width: columnWidths[index], - child: const Divider( - color: ColorsManager.boxDivider, - thickness: 1, - height: 1, + child: SizedBox( + child: Padding( + padding: const EdgeInsets.only( + left: 15, right: 10), + child: row[index], + ), ), ); }), ), - ], - ); - }, - ), + ), + ), + if (rowIndex < widget.rows.length - 1) + Row( + children: List.generate( + widget.titles.length, (index) { + return SizedBox( + width: columnWidths[index], + child: const Divider( + color: ColorsManager.boxDivider, + thickness: 1, + height: 1, + ), + ); + }), + ), + ], + ); + }, ), ), - ], - ), + ), + ], ), ), ), ); } } - - - - // Widget build(BuildContext context) { - // return Scaffold( - // body: SingleChildScrollView( - // scrollDirection: Axis.horizontal, - // child: SingleChildScrollView( - // scrollDirection: Axis.vertical, - // child: Column( - // children: [ - // // Header Row with Resizable Columns - // Container( - // color: Colors.green, - // child: Row( - // children: List.generate(widget.titles.length, (index) { - // return Row( - // children: [ - // Container( - // width: columnWidths[index], - // decoration: const BoxDecoration( - // color: Colors.green, - // ), - // child: Text( - // widget.titles[index], - // style: TextStyle(fontWeight: FontWeight.bold), - // textAlign: TextAlign.center, - // ), - // ), - // GestureDetector( - // onHorizontalDragUpdate: (details) { - // setState(() { - // columnWidths[index] = (columnWidths[index] + - // details.delta.dx) - // .clamp(50.0, 300.0); // Minimum & Maximum size - // }); - // }, - // child: MouseRegion( - // cursor: SystemMouseCursors - // .resizeColumn, // Set the cursor to resize - // child: Container( - // color: Colors.green, - // child: Container( - // color: Colors.black, - // width: 1, - // height: 50, // Height of the header cell - // ), - // ), - // ), - // ), - // ], - // ); - // }), - // ), - // ), - // // Data Rows - // ...widget.rows.map((row) { - // return Row( - // children: List.generate(row.length, (index) { - // return Container( - // width: columnWidths[index], - // child: row[index], - // ); - // }), - // ); - // }).toList(), - // ], - // ), - // ), - // ), - // ); - // } \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart index 1c93d44f..dae47196 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart @@ -107,7 +107,6 @@ class UsersPage extends StatelessWidget { builder: (context, state) { final screenSize = MediaQuery.of(context).size; final _blocRole = BlocProvider.of(context); - if (state is UsersLoadingState) { _blocRole.add(ChangePage(_blocRole.currentPage)); return const Center(child: CircularProgressIndicator()); @@ -189,8 +188,6 @@ class UsersPage extends StatelessWidget { const SizedBox(height: 25), DynamicTableScreen( onFilter: (columnIndex) { - _blocRole.add(FilterClearEvent()); - if (columnIndex == 0) { showNameMenu( context: context, @@ -210,11 +207,12 @@ class UsersPage extends StatelessWidget { if (columnIndex == 2) { final Map checkboxStates = { for (var item in _blocRole.jobTitle) - item: false, // Initialize with false + item: _blocRole.selectedJobTitles.contains(item), }; final RenderBox overlay = Overlay.of(context) .context .findRenderObject() as RenderBox; + showPopUpFilterMenu( position: RelativeRect.fromLTRB( overlay.size.width / 4, @@ -225,26 +223,28 @@ class UsersPage extends StatelessWidget { list: _blocRole.jobTitle, context: context, checkboxStates: checkboxStates, + isSelected: _blocRole.currentSortOrder, onOkPressed: () { + _blocRole.add(FilterClearEvent()); final selectedItems = checkboxStates.entries .where((entry) => entry.value) .map((entry) => entry.key) .toList(); Navigator.of(context).pop(); - _blocRole.add(FilterUsersByJobEvent(selectedItems)); + _blocRole.add(FilterUsersByJobEvent( + selectedJob: selectedItems, + sortOrder: _blocRole.currentSortOrder, + )); }, - onSortAtoZ: () { - context - .read() - .add(const SortUsersByNameAsc()); + onSortAtoZ: (v) { + _blocRole.currentSortOrder = v; }, - onSortZtoA: () { - context - .read() - .add(const SortUsersByNameDesc()); + onSortZtoA: (v) { + _blocRole.currentSortOrder = v; }, ); } + if (columnIndex == 3) { final Map checkboxStates = { for (var item in _blocRole.roleTypes) @@ -263,32 +263,31 @@ class UsersPage extends StatelessWidget { list: _blocRole.roleTypes, context: context, checkboxStates: checkboxStates, + isSelected: _blocRole.currentSortOrder, onOkPressed: () { + _blocRole.add(FilterClearEvent()); final selectedItems = checkboxStates.entries .where((entry) => entry.value) .map((entry) => entry.key) .toList(); Navigator.of(context).pop(); - context - .read() - .add(FilterUsersByRoleEvent(selectedItems)); + context.read().add( + FilterUsersByRoleEvent( + selectedRoles: selectedItems, + sortOrder: _blocRole.currentSortOrder)); }, - onSortAtoZ: () { - context - .read() - .add(const SortUsersByNameAsc()); + onSortAtoZ: (v) { + _blocRole.currentSortOrder = v; }, - onSortZtoA: () { - context - .read() - .add(const SortUsersByNameDesc()); + onSortZtoA: (v) { + _blocRole.currentSortOrder = v; }, ); } if (columnIndex == 4) { showDateFilterMenu( context: context, - isSelected: _blocRole.currentSortOrderDate, + isSelected: _blocRole.currentSortOrder, aToZTap: () { context .read() @@ -319,32 +318,30 @@ class UsersPage extends StatelessWidget { list: _blocRole.createdBy, context: context, checkboxStates: checkboxStates, + isSelected: _blocRole.currentSortOrder, onOkPressed: () { + _blocRole.add(FilterClearEvent()); final selectedItems = checkboxStates.entries .where((entry) => entry.value) .map((entry) => entry.key) .toList(); Navigator.of(context).pop(); - _blocRole - .add(FilterUsersByCreatedEvent(selectedItems)); + _blocRole.add(FilterUsersByCreatedEvent( + selectedCreatedBy: selectedItems, + sortOrder: _blocRole.currentSortOrder)); }, - onSortAtoZ: () { - context - .read() - .add(const SortUsersByNameAsc()); + onSortAtoZ: (v) { + _blocRole.currentSortOrder = v; }, - onSortZtoA: () { - context - .read() - .add(const SortUsersByNameDesc()); + onSortZtoA: (v) { + _blocRole.currentSortOrder = v; }, ); } - if (columnIndex == 7) { final Map checkboxStates = { for (var item in _blocRole.status) - item: _blocRole.selectedCreatedBy.contains(item), + item: _blocRole.selectedStatuses.contains(item), }; final RenderBox overlay = Overlay.of(context) .context @@ -359,24 +356,24 @@ class UsersPage extends StatelessWidget { list: _blocRole.status, context: context, checkboxStates: checkboxStates, + isSelected: _blocRole.currentSortOrder, onOkPressed: () { + _blocRole.add(FilterClearEvent()); + final selectedItems = checkboxStates.entries .where((entry) => entry.value) .map((entry) => entry.key) .toList(); Navigator.of(context).pop(); - _blocRole - .add(FilterUsersByCreatedEvent(selectedItems)); + _blocRole.add(FilterUsersByDeActevateEvent( + selectedActivate: selectedItems, + sortOrder: _blocRole.currentSortOrder)); }, - onSortAtoZ: () { - context - .read() - .add(const SortUsersByNameAsc()); + onSortAtoZ: (v) { + _blocRole.currentSortOrder = v; }, - onSortZtoA: () { - context - .read() - .add(const SortUsersByNameDesc()); + onSortZtoA: (v) { + _blocRole.currentSortOrder = v; }, ); } @@ -412,8 +409,8 @@ class UsersPage extends StatelessWidget { rows: state.users.map((user) { return [ Text('${user.firstName} ${user.lastName}'), - Text(user.email ), - Text(user.jobTitle ?? ''), + Text(user.email), + Text(user.jobTitle ?? '-'), Text(user.roleType ?? ''), Text(user.createdDate ?? ''), Text(user.createdTime ?? ''), @@ -476,11 +473,17 @@ class UsersPage extends StatelessWidget { barrierDismissible: false, builder: (BuildContext context) { return DeleteUserDialog( - onTapDelete: () { + onTapDelete: () async { + try { _blocRole.add(DeleteUserEvent( user.uuid, context)); - }, - ); + await Future.delayed( + const Duration(seconds: 2)); + return true; + } catch (e) { + return false; + } + }); }, ).then((v) { if (v != null) { @@ -504,6 +507,7 @@ class UsersPage extends StatelessWidget { SizedBox( width: 500, child: NumberPagination( + visiblePagesCount: 4, buttonRadius: 10, selectedButtonColor: ColorsManager.secondaryColor, buttonUnSelectedBorderColor: diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index ab05523f..527010d0 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -71,8 +71,8 @@ class UserPermissionApi { "firstName": firstName, "lastName": lastName, "email": email, - "jobTitle": jobTitle != '' ? jobTitle : " ", - "phoneNumber": phoneNumber != '' ? phoneNumber : " ", + "jobTitle": jobTitle != '' ? jobTitle : null, + "phoneNumber": phoneNumber != '' ? phoneNumber : null, "roleUuid": roleUuid, "projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c", "spaceUuids": spaceUuids, @@ -119,13 +119,8 @@ class UserPermissionApi { ); return response ?? 'Unknown error occurred'; } on DioException catch (e) { - if (e.response != null) { - final errorMessage = e.response?.data['error']; - return errorMessage is String - ? errorMessage - : 'Error occurred while checking email'; - } - return 'Error occurred while checking email'; + final errorMessage = e.response?.data['error']; + return errorMessage; } catch (e) { return e.toString(); } From eb7eeebf1860b0b3fcdcc751542fa983b526409e Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 13 Jan 2025 11:07:18 +0300 Subject: [PATCH 086/175] fixes add user view and table and user status --- .../add_user_dialog/view/add_user_dialog.dart | 3 +- .../add_user_dialog/view/basics_view.dart | 46 ++++++++++--------- .../view/permission_management.dart | 6 +-- .../view/popup_menu_filter.dart | 4 +- .../users_table/bloc/user_table_bloc.dart | 2 +- .../users_table/view/user_table.dart | 8 ++-- 6 files changed, 37 insertions(+), 32 deletions(-) diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart index f29aec8e..700e8f46 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart @@ -237,10 +237,11 @@ class _AddNewUserDialogState extends State { onTap: () { setState(() { currentStep = step; - bloc.add(CheckStepStatus(isEditUser: false)); + bloc.add(const CheckStepStatus(isEditUser: false)); if (step3 == 3) { bloc.add(const CheckRoleStepStatus()); } + }); }, child: Column( diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart index c5025fc3..7261ba50 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart @@ -47,7 +47,9 @@ class BasicsView extends StatelessWidget { ), Row( children: [ - Expanded( + SizedBox( + width: MediaQuery.of(context).size.width * 0.18, + height: MediaQuery.of(context).size.width * 0.08, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -76,12 +78,12 @@ class BasicsView extends StatelessWidget { child: TextFormField( style: const TextStyle(color: ColorsManager.blackColor), - onChanged: (value) { - Future.delayed(const Duration(milliseconds: 200), - () { - _blocRole.add(ValidateBasicsStep()); - }); - }, + // onChanged: (value) { + // Future.delayed(const Duration(milliseconds: 200), + // () { + // _blocRole.add(const ValidateBasicsStep()); + // }); + // }, controller: _blocRole.firstNameController, decoration: inputTextFormDeco( hintText: "Enter first name", @@ -103,7 +105,9 @@ class BasicsView extends StatelessWidget { ), ), const SizedBox(width: 10), - Expanded( + SizedBox( + width: MediaQuery.of(context).size.width * 0.18, + height: MediaQuery.of(context).size.width * 0.08, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -128,12 +132,12 @@ class BasicsView extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( - onChanged: (value) { - Future.delayed(const Duration(milliseconds: 200), - () { - _blocRole.add(ValidateBasicsStep()); - }); - }, + // onChanged: (value) { + // Future.delayed(const Duration(milliseconds: 200), + // () { + // _blocRole.add(ValidateBasicsStep()); + // }); + // }, controller: _blocRole.lastNameController, style: const TextStyle(color: Colors.black), decoration: @@ -186,13 +190,13 @@ class BasicsView extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: TextFormField( enabled: userId != '' ? false : true, - onChanged: (value) { - Future.delayed(const Duration(milliseconds: 200), () { - _blocRole.add(CheckStepStatus( - isEditUser: userId != '' ? false : true)); - _blocRole.add(ValidateBasicsStep()); - }); - }, + // onChanged: (value) { + // Future.delayed(const Duration(milliseconds: 200), () { + // _blocRole.add(CheckStepStatus( + // isEditUser: userId != '' ? false : true)); + // _blocRole.add(ValidateBasicsStep()); + // }); + // }, controller: _blocRole.emailController, style: const TextStyle(color: ColorsManager.blackColor), decoration: inputTextFormDeco(hintText: "name@example.com") diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart index 266d431e..c5c38e76 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart @@ -128,7 +128,7 @@ class _PermissionManagementState extends State { ), const SizedBox(width: 8), Text( - option.title, + ' ${option.title[0].toUpperCase()}${option.title.substring(1)}', style: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w700, fontSize: 12, @@ -184,7 +184,7 @@ class _PermissionManagementState extends State { ), const SizedBox(width: 8), Text( - subOption.title, + ' ${subOption.title[0].toUpperCase()}${subOption.title.substring(1)}', style: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w700, fontSize: 12, @@ -246,7 +246,7 @@ class _PermissionManagementState extends State { ), const SizedBox(width: 8), Text( - child.title, + ' ${child.title[0].toUpperCase()}${child.title.substring(1)}', style: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w400, fontSize: 12, diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart index c3a245c1..80228657 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart @@ -5,8 +5,8 @@ import 'package:syncrow_web/utils/style.dart'; Future showPopUpFilterMenu({ required BuildContext context, - required Function(String value)? onSortAtoZ, // Accept a parameter - required Function(String value)? onSortZtoA, // Accept a parameter + required Function(String value)? onSortAtoZ, + required Function(String value)? onSortZtoA, Function()? cancelButton, required Map checkboxStates, required RelativeRect position, diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart index b1131ca4..c50667be 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart @@ -93,7 +93,7 @@ class UserTableBloc extends Bloc { try { emit(UsersLoadingState()); bool res = await UserPermissionApi().changeUserStatusById( - event.userId, event.newStatus == "disabled" ? true : false); + event.userId, event.newStatus == "disabled" ? false : true); if (res == true) { add(const GetUsers()); // users = users.map((user) { diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart index ca641cbe..92229643 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart @@ -46,7 +46,7 @@ class _DynamicTableScreenState extends State 0.12; // 20% of screen width for the second column } else if (index == 9) { return newScreenWidth * - 0.2; // 25% of screen width for the tenth column + 0.1; // 25% of screen width for the tenth column } return newScreenWidth * 0.09; // Default to 10% of screen width for other columns @@ -57,14 +57,14 @@ class _DynamicTableScreenState extends State @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; - if (columnWidths.every((width) => width == 120.0)) { + if (columnWidths.every((width) => width == screenWidth * 7)) { columnWidths = List.generate(widget.titles.length, (index) { if (index == 1) { return screenWidth * 0.11; } else if (index == 9) { - return screenWidth * 0.2; + return screenWidth * 0.1; } - return screenWidth * 0.11; + return screenWidth * 0.09; }); setState(() {}); } From acbb6ca7c09ae14c60dc634ad4d86b76fbd7df5e Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 13 Jan 2025 12:12:22 +0400 Subject: [PATCH 087/175] removed try catch --- .../bloc/space_management_bloc.dart | 41 ++++---- lib/services/space_model_mang_api.dart | 97 ++++++++----------- 2 files changed, 64 insertions(+), 74 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 6458bdf6..20f642fd 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -76,19 +76,24 @@ class SpaceManagementBloc Future> fetchSpaceModels( SpaceManagementState previousState) async { - List prevSpaceModels = []; + try { + List prevSpaceModels = []; - if (previousState is SpaceManagementLoaded || previousState is BlankState) { - prevSpaceModels = List.from( - (previousState as dynamic).spaceModels ?? [], - ); + if (previousState is SpaceManagementLoaded || + previousState is BlankState) { + prevSpaceModels = List.from( + (previousState as dynamic).spaceModels ?? [], + ); + } + + if (prevSpaceModels.isEmpty) { + prevSpaceModels = await _spaceModelApi.listSpaceModels(page: 1); + } + + return prevSpaceModels; + } catch (e) { + return []; } - - if (prevSpaceModels.isEmpty) { - prevSpaceModels = await _spaceModelApi.listSpaceModels(page: 1); - } - - return prevSpaceModels; } void _onloadProducts() async { @@ -184,6 +189,7 @@ class SpaceManagementBloc LoadCommunityAndSpacesEvent event, Emitter emit, ) async { + var prevState = state; emit(SpaceManagementLoading()); try { _onloadProducts(); @@ -205,12 +211,11 @@ class SpaceManagementBloc }).toList(), ); - List spaceModels = - await _spaceModelApi.listSpaceModels(page: 1); + final prevSpaceModels = await fetchSpaceModels(prevState); emit(SpaceManagementLoaded( communities: updatedCommunities, products: _cachedProducts ?? [], - spaceModels: spaceModels)); + spaceModels: prevSpaceModels)); } catch (e) { emit(SpaceManagementError('Error loading communities and spaces: $e')); } @@ -487,6 +492,8 @@ class SpaceManagementBloc SpaceModelLoadEvent event, Emitter emit) async { emit(SpaceManagementLoading()); try { + var prevState = state; + List communities = await _api.fetchCommunities(); List updatedCommunities = await Future.wait( @@ -505,12 +512,12 @@ class SpaceManagementBloc }).toList(), ); - List spaceModels = - await _spaceModelApi.listSpaceModels(page: 1); + var prevSpaceModels = await fetchSpaceModels(prevState); + emit(SpaceModelLoaded( communities: updatedCommunities, products: _cachedProducts ?? [], - spaceModels: spaceModels)); + spaceModels: prevSpaceModels)); } catch (e) { emit(SpaceManagementError('Error loading communities and spaces: $e')); } diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart index 19752aea..b13d62ad 100644 --- a/lib/services/space_model_mang_api.dart +++ b/lib/services/space_model_mang_api.dart @@ -7,71 +7,54 @@ import 'package:syncrow_web/utils/constants/temp_const.dart'; class SpaceModelManagementApi { Future> listSpaceModels({int page = 1}) async { - try { - List spaceModels = []; - bool hasNext = true; - while (hasNext) { - await HTTPService().get( - path: ApiEndpoints.listSpaceModels - .replaceAll('{projectId}', TempConst.projectId), - queryParameters: {'page': page}, - expectedResponseModel: (json) { - List jsonData = json['data']; - hasNext = json['hasNext'] ?? false; - int currentPage = json['page'] ?? 1; - List spaceModelList = jsonData.map((jsonItem) { - return SpaceTemplateModel.fromJson(jsonItem); - }).toList(); + List spaceModels = []; + bool hasNext = true; + while (hasNext) { + await HTTPService().get( + path: ApiEndpoints.listSpaceModels + .replaceAll('{projectId}', TempConst.projectId), + queryParameters: {'page': page}, + expectedResponseModel: (json) { + List jsonData = json['data']; + hasNext = json['hasNext'] ?? false; + int currentPage = json['page'] ?? 1; + List spaceModelList = jsonData.map((jsonItem) { + return SpaceTemplateModel.fromJson(jsonItem); + }).toList(); - spaceModels.addAll(spaceModelList); - page = currentPage + 1; - return spaceModelList; - }, - ); - } - return spaceModels; - } catch (e) { - debugPrint('Error fetching space models: $e'); - return []; + spaceModels.addAll(spaceModelList); + page = currentPage + 1; + return spaceModelList; + }, + ); } + return spaceModels; } Future createSpaceModel( CreateSpaceTemplateBodyModel spaceModel) async { - try { - final response = await HTTPService().post( - path: ApiEndpoints.createSpaceModel - .replaceAll('{projectId}', TempConst.projectId), - showServerMessage: true, - body: spaceModel.toJson(), - expectedResponseModel: (json) { - return SpaceTemplateModel.fromJson(json['data']); - }, - ); - return response; - } catch (e) { - debugPrint('Error creating space model: $e'); - return null; - } + final response = await HTTPService().post( + path: ApiEndpoints.createSpaceModel + .replaceAll('{projectId}', TempConst.projectId), + showServerMessage: true, + body: spaceModel.toJson(), + expectedResponseModel: (json) { + return SpaceTemplateModel.fromJson(json['data']); + }, + ); + return response; } Future getSpaceModel(String spaceModelUuid) async { - try { - final response = await HTTPService().get( - path: ApiEndpoints.getSpaceModel - .replaceAll('{projectId}', TempConst.projectId) - .replaceAll('{spaceModelUuid}', spaceModelUuid), - showServerMessage: true, - expectedResponseModel: (json) { - debugPrint('Response JSON: $json'); - - return SpaceTemplateModel.fromJson(json['data']); - }, - ); - return response; - } catch (e) { - debugPrint('Error getting space model: $e'); - return null; - } + final response = await HTTPService().get( + path: ApiEndpoints.getSpaceModel + .replaceAll('{projectId}', TempConst.projectId) + .replaceAll('{spaceModelUuid}', spaceModelUuid), + showServerMessage: true, + expectedResponseModel: (json) { + return SpaceTemplateModel.fromJson(json['data']); + }, + ); + return response; } } From 210fbf7497aab1b45bb3143cb493ea60a397f184 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 13 Jan 2025 12:16:49 +0400 Subject: [PATCH 088/175] initialize with const --- .../assign_tag_models/bloc/assign_tag_model_event.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart index 697b1c2a..38642d96 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart @@ -10,16 +10,16 @@ abstract class AssignTagModelEvent extends Equatable { } class InitializeTagModels extends AssignTagModelEvent { - final List? initialTags; + final List initialTags; final List addedProducts; const InitializeTagModels({ - required this.initialTags, + this.initialTags = const [], required this.addedProducts, }); @override - List get props => [initialTags ?? [], addedProducts]; + List get props => [initialTags, addedProducts]; } class UpdateTag extends AssignTagModelEvent { From a4e7f30411f245b4265a36c17437f1f47096e440 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 13 Jan 2025 14:14:09 +0400 Subject: [PATCH 089/175] moved pagination to bloc --- .../bloc/space_management_bloc.dart | 16 +++++++-- .../views/assign_tag_models_dialog.dart | 4 +-- lib/services/space_model_mang_api.dart | 35 +++++++------------ 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 20f642fd..26e444ca 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -6,7 +6,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; import 'package:syncrow_web/services/product_api.dart'; @@ -77,6 +76,7 @@ class SpaceManagementBloc Future> fetchSpaceModels( SpaceManagementState previousState) async { try { + List allSpaces = []; List prevSpaceModels = []; if (previousState is SpaceManagementLoaded || @@ -87,10 +87,22 @@ class SpaceManagementBloc } if (prevSpaceModels.isEmpty) { + bool hasNext = true; + int page = 1; + + while (hasNext) { + final spaces = await _spaceModelApi.listSpaceModels(page: page); + if (spaces.isNotEmpty) { + allSpaces.addAll(spaces); + page++; + } else { + hasNext = false; + } + } prevSpaceModels = await _spaceModelApi.listSpaceModels(page: 1); } - return prevSpaceModels; + return allSpaces; } catch (e) { return []; } diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index f4f2276f..1b162832 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -16,7 +16,7 @@ import 'package:syncrow_web/utils/color_manager.dart'; class AssignTagModelsDialog extends StatelessWidget { final List? products; final List? subspaces; - final List? initialTags; + final List initialTags; final ValueChanged>? onTagsAssigned; final List addedProducts; final List? allTags; @@ -28,7 +28,7 @@ class AssignTagModelsDialog extends StatelessWidget { required this.products, required this.subspaces, required this.addedProducts, - this.initialTags, + required this.initialTags, this.onTagsAssigned, this.allTags, required this.spaceName, diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart index b13d62ad..ee241189 100644 --- a/lib/services/space_model_mang_api.dart +++ b/lib/services/space_model_mang_api.dart @@ -1,4 +1,3 @@ -import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; @@ -7,28 +6,18 @@ import 'package:syncrow_web/utils/constants/temp_const.dart'; class SpaceModelManagementApi { Future> listSpaceModels({int page = 1}) async { - List spaceModels = []; - bool hasNext = true; - while (hasNext) { - await HTTPService().get( - path: ApiEndpoints.listSpaceModels - .replaceAll('{projectId}', TempConst.projectId), - queryParameters: {'page': page}, - expectedResponseModel: (json) { - List jsonData = json['data']; - hasNext = json['hasNext'] ?? false; - int currentPage = json['page'] ?? 1; - List spaceModelList = jsonData.map((jsonItem) { - return SpaceTemplateModel.fromJson(jsonItem); - }).toList(); - - spaceModels.addAll(spaceModelList); - page = currentPage + 1; - return spaceModelList; - }, - ); - } - return spaceModels; + final response = await HTTPService().get( + path: ApiEndpoints.listSpaceModels + .replaceAll('{projectId}', TempConst.projectId), + queryParameters: {'page': page}, + expectedResponseModel: (json) { + List jsonData = json['data']; + return jsonData.map((jsonItem) { + return SpaceTemplateModel.fromJson(jsonItem); + }).toList(); + }, + ); + return response; } Future createSpaceModel( From 20a9f19480314d00a51b2536e7da7f79f3cc4834 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 13 Jan 2025 14:36:17 +0300 Subject: [PATCH 090/175] check if title is not empty and remove nullable --- .../add_user_dialog/view/add_user_dialog.dart | 2 +- .../users_page/add_user_dialog/view/basics_view.dart | 1 - .../add_user_dialog/view/permission_management.dart | 6 +++--- .../add_user_dialog/view/popup_menu_filter.dart | 12 ++++++------ 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart index 700e8f46..ec35b3fd 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart @@ -155,7 +155,7 @@ class _AddNewUserDialogState extends State { userId: '', ); case 2: - return SpacesAccessView(); + return const SpacesAccessView(); case 3: return const RolesAndPermission(); default: diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart index 7261ba50..53d9a333 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart @@ -4,7 +4,6 @@ import 'package:intl_phone_field/countries.dart'; import 'package:intl_phone_field/country_picker_dialog.dart'; import 'package:intl_phone_field/intl_phone_field.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart'; -import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart'; import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart index c5c38e76..aee84ed4 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart @@ -128,7 +128,7 @@ class _PermissionManagementState extends State { ), const SizedBox(width: 8), Text( - ' ${option.title[0].toUpperCase()}${option.title.substring(1)}', + ' ${option.title.isNotEmpty ? option.title[0].toUpperCase() : ''}${option.title.substring(1)}', style: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w700, fontSize: 12, @@ -184,7 +184,7 @@ class _PermissionManagementState extends State { ), const SizedBox(width: 8), Text( - ' ${subOption.title[0].toUpperCase()}${subOption.title.substring(1)}', + ' ${subOption.title.isNotEmpty ? subOption.title[0].toUpperCase() : ''}${subOption.title.substring(1)}', style: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w700, fontSize: 12, @@ -246,7 +246,7 @@ class _PermissionManagementState extends State { ), const SizedBox(width: 8), Text( - ' ${child.title[0].toUpperCase()}${child.title.substring(1)}', + ' ${child.title.isNotEmpty ? child.title[0].toUpperCase() : ''}${child.title.substring(1)}', style: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w400, fontSize: 12, diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart index 80228657..120a1a3a 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart @@ -5,8 +5,8 @@ import 'package:syncrow_web/utils/style.dart'; Future showPopUpFilterMenu({ required BuildContext context, - required Function(String value)? onSortAtoZ, - required Function(String value)? onSortZtoA, + required Function(String value) onSortAtoZ, + required Function(String value) onSortZtoA, Function()? cancelButton, required Map checkboxStates, required RelativeRect position, @@ -35,9 +35,9 @@ Future showPopUpFilterMenu({ setState(() { if (isSelected == 'Asc') { isSelected = null; - onSortAtoZ?.call(''); + onSortAtoZ.call(''); } else { - onSortAtoZ?.call('Asc'); + onSortAtoZ.call('Asc'); isSelected = 'Asc'; } }); @@ -60,9 +60,9 @@ Future showPopUpFilterMenu({ setState(() { if (isSelected == 'Desc') { isSelected = null; - onSortZtoA?.call(''); + onSortZtoA.call(''); } else { - onSortZtoA?.call('Desc'); + onSortZtoA.call('Desc'); isSelected = 'Desc'; } }); From db7eaa53afadeb986fd2c0793b524892a9ceda14 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 13 Jan 2025 15:40:38 +0300 Subject: [PATCH 091/175] check type isNotEmpty --- .../users_page/add_user_dialog/view/role_dropdown.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart index c8126dbd..3a5ac65c 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart @@ -60,7 +60,7 @@ class _RoleDropdownState extends State { return DropdownMenuItem( value: role.uuid, child: Text( - ' ${role.type[0].toUpperCase()}${role.type.substring(1)}', + ' ${role.type.isNotEmpty ? role.type[0].toUpperCase() : ''}${role.type.substring(1)}', ), ); }).toList(), From 12df07e681ad3aa6a480f058c9b58c856c370912 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 14 Jan 2025 12:14:48 +0400 Subject: [PATCH 092/175] changed chip to list --- .../view/link_space_model_dialog.dart | 2 +- .../space_model/view/space_model_page.dart | 2 +- .../widgets/dynamic_product_widget.dart | 66 +++++++++ .../widgets/dynamic_room_widget.dart | 58 ++++++++ .../widgets/ellipsis_item_widget.dart | 25 ++++ .../widgets/flexible_item_widget.dart | 44 ++++++ .../space_model/widgets/room_name_widget.dart | 27 ++++ .../widgets/space_model_card_widget.dart | 127 +++++++----------- .../widgets/subspace_chip_widget.dart | 30 +++-- 9 files changed, 288 insertions(+), 93 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/widgets/dynamic_product_widget.dart create mode 100644 lib/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart create mode 100644 lib/pages/spaces_management/space_model/widgets/ellipsis_item_widget.dart create mode 100644 lib/pages/spaces_management/space_model/widgets/flexible_item_widget.dart create mode 100644 lib/pages/spaces_management/space_model/widgets/room_name_widget.dart 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 2ab969df..69023857 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 @@ -72,7 +72,7 @@ class LinkSpaceModelDialog extends StatelessWidget { ), borderRadius: BorderRadius.circular(8.0), ), - child: SpaceModelCardWidget(model: model), + child: SpaceModelCardWidget(model: model,), ), ); }, diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index a76543ae..eab43e08 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -69,7 +69,7 @@ class SpaceModelPage extends StatelessWidget { final model = spaceModels[index]; return Container( margin: const EdgeInsets.all(8.0), - child: SpaceModelCardWidget(model: model), + child: SpaceModelCardWidget(model:model), ); }, ), diff --git a/lib/pages/spaces_management/space_model/widgets/dynamic_product_widget.dart b/lib/pages/spaces_management/space_model/widgets/dynamic_product_widget.dart new file mode 100644 index 00000000..4f42e3bf --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dynamic_product_widget.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/ellipsis_item_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/flexible_item_widget.dart'; + +import 'package:syncrow_web/utils/color_manager.dart'; + +class DynamicProductWidget extends StatelessWidget { + final Map productTagCount; + final double maxWidth; + final double maxHeight; + + const DynamicProductWidget({ + Key? key, + required this.productTagCount, + required this.maxWidth, + required this.maxHeight, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + const double itemSpacing = 8.0; + const double lineSpacing = 8.0; + const double textPadding = 16.0; + const double itemHeight = 40.0; + + List productWidgets = []; + double currentLineWidth = 0.0; + double currentHeight = itemHeight; + + for (final product in productTagCount.entries) { + final String prodType = product.key; + final int count = product.value; + + final TextPainter textPainter = TextPainter( + text: TextSpan( + text: 'x$count', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.spaceColor), + ), + textDirection: TextDirection.ltr, + )..layout(); + + final double itemWidth = textPainter.width + textPadding + 20; + + if (currentLineWidth + itemWidth + itemSpacing > maxWidth) { + currentHeight += itemHeight + lineSpacing; + if (currentHeight > maxHeight) { + productWidgets.add(const EllipsisItemWidget()); + break; + } + currentLineWidth = 0.0; + } + + productWidgets.add(FlexibleItemWidget(prodType: prodType, count: count)); + currentLineWidth += itemWidth + itemSpacing; + } + + return Wrap( + spacing: itemSpacing, + runSpacing: lineSpacing, + children: productWidgets, + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart b/lib/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart new file mode 100644 index 00000000..e24c7704 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/room_name_widget.dart'; + +class DynamicRoomWidget extends StatelessWidget { + final List? subspaceModels; + final double maxWidth; + final double maxHeight; + + const DynamicRoomWidget({ + Key? key, + required this.subspaceModels, + required this.maxWidth, + required this.maxHeight, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + const double itemSpacing = 8.0; + const double lineSpacing = 8.0; + const double textPadding = 16.0; + const double itemHeight = 30.0; + + List roomWidgets = []; + double currentLineWidth = 0.0; + double currentHeight = itemHeight; + + for (final subspace in subspaceModels!) { + final TextPainter textPainter = TextPainter( + text: TextSpan( + text: subspace.subspaceName, + style: const TextStyle(fontSize: 16), + ), + textDirection: TextDirection.ltr, + )..layout(); + + final double itemWidth = textPainter.width + textPadding; + + if (currentLineWidth + itemWidth + itemSpacing > maxWidth) { + currentHeight += itemHeight + lineSpacing; + if (currentHeight > maxHeight) { + roomWidgets.add(const RoomNameWidget(name: "...")); + break; + } + currentLineWidth = 0.0; + } + + roomWidgets.add(RoomNameWidget(name: subspace.subspaceName)); + currentLineWidth += itemWidth + itemSpacing; + } + + return Wrap( + spacing: itemSpacing, + runSpacing: lineSpacing, + children: roomWidgets, + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/ellipsis_item_widget.dart b/lib/pages/spaces_management/space_model/widgets/ellipsis_item_widget.dart new file mode 100644 index 00000000..7ede09a7 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/ellipsis_item_widget.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class EllipsisItemWidget extends StatelessWidget { + const EllipsisItemWidget({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: ColorsManager.transparentColor), + ), + child: Text( + "...", + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.spaceColor), + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/flexible_item_widget.dart b/lib/pages/spaces_management/space_model/widgets/flexible_item_widget.dart new file mode 100644 index 00000000..c28a82b8 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/flexible_item_widget.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class FlexibleItemWidget extends StatelessWidget { + final String prodType; + final int count; + + const FlexibleItemWidget({ + Key? key, + required this.prodType, + required this.count, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: ColorsManager.transparentColor), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + prodType, + width: 15, + height: 16, + ), + const SizedBox(width: 4), + Text( + 'x$count', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.spaceColor), + ), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/room_name_widget.dart b/lib/pages/spaces_management/space_model/widgets/room_name_widget.dart new file mode 100644 index 00000000..d59f8c1e --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/room_name_widget.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class RoomNameWidget extends StatelessWidget { + final String name; + + const RoomNameWidget({Key? key, required this.name}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: ColorsManager.transparentColor), + ), + child: Text( + name, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.spaceColor), + ), + ); + } +} 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 4471743b..ac8b49d0 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,7 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.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/subspace_chip_widget.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'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/room_name_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class SpaceModelCardWidget extends StatelessWidget { @@ -32,111 +33,81 @@ class SpaceModelCardWidget extends StatelessWidget { } return Container( + padding: const EdgeInsets.all(16.0), decoration: BoxDecoration( - color: ColorsManager.whiteColors, + color: Colors.white, borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( - color: ColorsManager.lightGrayColor.withOpacity(0.5), + color: Colors.grey.withOpacity(0.5), spreadRadius: 2, blurRadius: 5, offset: const Offset(0, 3), ), ], ), - padding: const EdgeInsets.fromLTRB(16.0, 14.0, 8.0, 8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Flexible( - child: Text( - model.modelName, - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - color: ColorsManager.blackColor, - fontWeight: FontWeight.bold, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), + Text( + model.modelName, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: Colors.black, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), const SizedBox(height: 10), Expanded( child: Row( - crossAxisAlignment: CrossAxisAlignment.start, children: [ + // Left Container Expanded( - child: Wrap( - spacing: 3.0, - runSpacing: 3.0, - children: [ - for (var subspace in model.subspaceModels! - .take(calculateTakeCount(context))) - SubspaceChipWidget(subspace: subspace.subspaceName), - ], + flex: 1, // Distribute space proportionally + child: Container( + padding: const EdgeInsets.all(8.0), + child: LayoutBuilder( + builder: (context, constraints) { + return Align( + alignment: Alignment.topLeft, + child: DynamicRoomWidget( + subspaceModels: model.subspaceModels, + maxWidth: constraints.maxWidth, + maxHeight: constraints.maxHeight, + ), + ); + }, + ), ), ), - if (productTagCount.isNotEmpty) - Container( - width: 1, - height: double.infinity, - color: ColorsManager.softGray, - margin: const EdgeInsets.symmetric(horizontal: 4.0), - ), - const SizedBox(width: 7), + Container( + width: 1.0, // Thickness of the line + color: ColorsManager.softGray, // Subtle grey color + margin: const EdgeInsets.symmetric( + vertical: 6.0), // Top and bottom spacing + ), // Right Container Expanded( - child: Wrap( - spacing: 4.0, - runSpacing: 4.0, - children: productTagCount.entries.map((entry) { - final prodType = entry.key; - final count = entry.value; - - return Chip( - label: Row( - mainAxisSize: MainAxisSize.min, - children: [ - SvgPicture.asset( - prodType, - width: 15, - height: 16, - ), - const SizedBox(width: 4), - Text( - 'x$count', // Product count - style: const TextStyle(fontSize: 12), - ), - ], - ), - backgroundColor: ColorsManager.textFieldGreyColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: const BorderSide( - color: ColorsManager.transparentColor, - width: 0, - ), - ), - ); - }).toList(), + flex: 1, // Distribute space proportionally + child: Container( + padding: const EdgeInsets.all(8.0), + child: LayoutBuilder( + builder: (context, constraints) { + return Align( + alignment: Alignment.topLeft, + child: DynamicProductWidget( + productTagCount: productTagCount, + maxWidth: constraints.maxWidth, + maxHeight: constraints.maxHeight)); + }, + ), ), ), ], ), ), - const SizedBox(height: 5), ], ), ); } - - int calculateTakeCount(BuildContext context) { - double screenWidth = MediaQuery.of(context).size.width; - // Adjust the count based on the screen width - if (screenWidth > 1500) { - return 3; // For large screens - } else if (screenWidth > 1200) { - return 2; - } else { - return 1; // For smaller screens - } - } } diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart index 517448c8..8f987c51 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + class SubspaceChipWidget extends StatelessWidget { final String subspace; @@ -11,24 +14,25 @@ class SubspaceChipWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Chip( - label: Text( - subspace, - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - backgroundColor: ColorsManager.textFieldGreyColor, - labelStyle: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: ColorsManager.spaceColor), - shape: RoundedRectangleBorder( + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, borderRadius: BorderRadius.circular(8), - side: const BorderSide( + border: Border.all( color: ColorsManager.transparentColor, width: 0, ), ), + child: Text( + subspace, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.spaceColor), + ), ); } } From a220483310a6f64c6fd49488ecacbc87642b3be7 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 14 Jan 2025 12:22:51 +0400 Subject: [PATCH 093/175] revert back --- lib/utils/constants/temp_const.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/constants/temp_const.dart b/lib/utils/constants/temp_const.dart index bcd1a1d6..e5847b98 100644 --- a/lib/utils/constants/temp_const.dart +++ b/lib/utils/constants/temp_const.dart @@ -1,3 +1,3 @@ class TempConst { - static const projectId = '0685c781-df33-4cbf-bf65-9f4e835eb468'; + static const projectId = '0e62577c-06fa-41b9-8a92-99a21fbaf51c'; } From cf2690123e3e6757bd04c08188110ae4538ef3e3 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 14 Jan 2025 12:22:59 +0400 Subject: [PATCH 094/175] revert back --- .env.development | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.development b/.env.development index 1fd358ec..e77609dc 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,2 @@ ENV_NAME=development -BASE_URL=http://localhost:4001 \ No newline at end of file +BASE_URL=https://syncrow-dev.azurewebsites.net \ No newline at end of file From 0bb24604bc1803df0fb50549ffff6ff2b01b78ba Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 15 Jan 2025 09:39:26 +0400 Subject: [PATCH 095/175] fixed subspace create --- .../views/create_subspace_model_dialog.dart | 26 +++++++----- .../bloc/create_space_model_event.dart | 1 + .../dialog/create_space_model_dialog.dart | 3 +- .../widgets/subspace_model_create_widget.dart | 40 +++++++++---------- 4 files changed, 38 insertions(+), 32 deletions(-) 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 4a290617..da149d47 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 @@ -21,18 +21,20 @@ class CreateSubSpaceModelDialog extends StatelessWidget { final List? allTags; final List? products; final SpaceTemplateModel? spaceModel; + final void Function(SpaceTemplateModel newModel)? onLoad; - const CreateSubSpaceModelDialog({ - Key? key, - required this.isEdit, - required this.dialogTitle, - this.existingSubSpaces, - required this.allTags, - required this.spaceName, - required this.spaceTagModels, - required this.products, - required this.spaceModel, - }) : super(key: key); + const CreateSubSpaceModelDialog( + {Key? key, + required this.isEdit, + required this.dialogTitle, + this.existingSubSpaces, + required this.allTags, + required this.spaceName, + required this.spaceTagModels, + required this.products, + required this.spaceModel, + this.onLoad}) + : super(key: key); @override Widget build(BuildContext context) { @@ -176,6 +178,7 @@ class CreateSubSpaceModelDialog extends StatelessWidget { CreateSpaceModelDialog( products: products, allTags: allTags, + onLoad: onLoad, spaceModel: SpaceTemplateModel( modelName: spaceName ?? '', subspaceModels: existingSubSpaces, @@ -203,6 +206,7 @@ class CreateSubSpaceModelDialog extends StatelessWidget { CreateSpaceModelDialog( products: products, allTags: allTags, + onLoad: onLoad, spaceModel: SpaceTemplateModel( modelName: spaceName ?? '', subspaceModels: subSpaces, diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index 9342c771..1d7f6012 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -21,6 +21,7 @@ class CreateSpaceTemplate extends CreateSpaceModelEvent { final SpaceTemplateModel spaceTemplate; final Function(SpaceTemplateModel)? onCreate; + const CreateSpaceTemplate({ required this.spaceTemplate, this.onCreate, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 51222ad8..64d5ec2f 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -110,11 +110,12 @@ class CreateSpaceModelDialog extends StatelessWidget { const SizedBox(height: 16), SubspaceModelCreate(context, subspaces: state.space.subspaceModels ?? [], + onLoad:onLoad, allTags: allTags, products: products, spaceModel: spaceModel, spaceTagModels: spaceModel?.tags ?? [], - spaceNameController: spaceNameController), + spaceNameController: spaceNameController,), const SizedBox(height: 10), TagChipDisplay(context, screenWidth: screenWidth, diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 832c9ea3..f62f15fa 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -14,17 +14,18 @@ class SubspaceModelCreate extends StatelessWidget { final List? allTags; final List? products; final SpaceTemplateModel? spaceModel; + final void Function(SpaceTemplateModel newModel)? onLoad; - const SubspaceModelCreate( - BuildContext context, { - Key? key, - required this.subspaces, - this.spaceTagModels, - required this.allTags, - required this.products, - required this.spaceModel, - required this.spaceNameController, - }) : super(key: key); + const SubspaceModelCreate(BuildContext context, + {Key? key, + required this.subspaces, + this.spaceTagModels, + required this.allTags, + required this.products, + required this.spaceModel, + required this.spaceNameController, + this.onLoad}) + : super(key: key); @override Widget build(BuildContext context) { @@ -53,6 +54,7 @@ class SubspaceModelCreate extends StatelessWidget { ? 'Create Sub-space' : 'Edit Sub-space', existingSubSpaces: subspaces, + onLoad: onLoad, ); }, ); @@ -85,7 +87,8 @@ class SubspaceModelCreate extends StatelessWidget { style: const TextStyle( color: ColorsManager.spaceColor), // Text color ), - backgroundColor: ColorsManager.whiteColors, // Chip background color + backgroundColor: + ColorsManager.whiteColors, // Chip background color shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), // Rounded chip @@ -110,6 +113,7 @@ class SubspaceModelCreate extends StatelessWidget { spaceTagModels: spaceTagModels, products: products, spaceModel: spaceModel, + onLoad: onLoad, ); }, ); @@ -117,16 +121,13 @@ class SubspaceModelCreate extends StatelessWidget { child: Chip( label: const Text( 'Edit', - style: TextStyle( - color: ColorsManager.spaceColor), + style: TextStyle(color: ColorsManager.spaceColor), ), - backgroundColor: - ColorsManager.whiteColors, + backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(16), - side: const BorderSide( - color: ColorsManager.spaceColor), + borderRadius: BorderRadius.circular(16), + side: + const BorderSide(color: ColorsManager.spaceColor), ), ), ), @@ -134,7 +135,6 @@ class SubspaceModelCreate extends StatelessWidget { ), ), ), - ); } } From 5975adb5e2458b42df4ff0e7769c4faa9610fba8 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 15 Jan 2025 10:22:42 +0400 Subject: [PATCH 096/175] cleaned subspace model create --- .../views/create_subspace_model_dialog.dart | 50 +----------- .../dialog/create_space_model_dialog.dart | 17 ++-- .../widgets/subspace_model_create_widget.dart | 78 ++++++------------- 3 files changed, 34 insertions(+), 111 deletions(-) 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 da149d47..29ed3846 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 @@ -2,38 +2,24 @@ 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/all_spaces/model/product_model.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/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateSubSpaceModelDialog extends StatelessWidget { final bool isEdit; final String dialogTitle; final List? existingSubSpaces; - final String? spaceName; - final List? spaceTagModels; - final List? allTags; - final List? products; - final SpaceTemplateModel? spaceModel; - final void Function(SpaceTemplateModel newModel)? onLoad; + final void Function(List newSubspaces)? onUpdate; const CreateSubSpaceModelDialog( {Key? key, required this.isEdit, required this.dialogTitle, this.existingSubSpaces, - required this.allTags, - required this.spaceName, - required this.spaceTagModels, - required this.products, - required this.spaceModel, - this.onLoad}) + this.onUpdate}) : super(key: key); @override @@ -171,21 +157,6 @@ class CreateSubSpaceModelDialog extends StatelessWidget { label: 'Cancel', onPressed: () async { Navigator.of(context).pop(); - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => - CreateSpaceModelDialog( - products: products, - allTags: allTags, - onLoad: onLoad, - spaceModel: SpaceTemplateModel( - modelName: spaceName ?? '', - subspaceModels: existingSubSpaces, - tags: spaceTagModels, - ), - ), - ); }, ), ), @@ -198,22 +169,7 @@ class CreateSubSpaceModelDialog extends StatelessWidget { .state .subSpaces; Navigator.of(context).pop(); - - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => - CreateSpaceModelDialog( - products: products, - allTags: allTags, - onLoad: onLoad, - spaceModel: SpaceTemplateModel( - modelName: spaceName ?? '', - subspaceModels: subSpaces, - tags: spaceTagModels, - ), - ), - ); + onUpdate!(subSpaces); }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 64d5ec2f..98c82f2a 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -108,14 +108,15 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ), const SizedBox(height: 16), - SubspaceModelCreate(context, - subspaces: state.space.subspaceModels ?? [], - onLoad:onLoad, - allTags: allTags, - products: products, - spaceModel: spaceModel, - spaceTagModels: spaceModel?.tags ?? [], - spaceNameController: spaceNameController,), + SubspaceModelCreate( + context, + subspaces: state.space.subspaceModels ?? [], + onSpaceModelUpdate: (updatedSubspaces) { + context + .read() + .add(AddSubspacesToSpaceTemplate(updatedSubspaces)); + }, + ), const SizedBox(height: 10), TagChipDisplay(context, screenWidth: screenWidth, diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index f62f15fa..7781bb5e 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -1,30 +1,16 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class SubspaceModelCreate extends StatelessWidget { final List subspaces; - final TextEditingController spaceNameController; - final List? spaceTagModels; - final List? allTags; - final List? products; - final SpaceTemplateModel? spaceModel; - final void Function(SpaceTemplateModel newModel)? onLoad; + final void Function(List newSubspaces)? + onSpaceModelUpdate; const SubspaceModelCreate(BuildContext context, - {Key? key, - required this.subspaces, - this.spaceTagModels, - required this.allTags, - required this.products, - required this.spaceModel, - required this.spaceNameController, - this.onLoad}) + {Key? key, required this.subspaces, this.onSpaceModelUpdate}) : super(key: key); @override @@ -37,27 +23,7 @@ class SubspaceModelCreate extends StatelessWidget { overlayColor: ColorsManager.transparentColor, ), onPressed: () async { - Navigator.of(context).pop(); - - await showDialog>( - barrierDismissible: false, - context: context, - builder: (BuildContext context) { - return CreateSubSpaceModelDialog( - allTags: allTags, - spaceName: spaceNameController.text, - spaceModel: spaceModel, - spaceTagModels: spaceTagModels, - products: products, - isEdit: true, - dialogTitle: subspaces.isEmpty - ? 'Create Sub-space' - : 'Edit Sub-space', - existingSubSpaces: subspaces, - onLoad: onLoad, - ); - }, - ); + await _openDialog(context, 'Create Sub-space'); }, child: const ButtonContentWidget( icon: Icons.add, @@ -99,24 +65,7 @@ class SubspaceModelCreate extends StatelessWidget { ), GestureDetector( onTap: () async { - Navigator.of(context).pop(); - await showDialog>( - barrierDismissible: false, - context: context, - builder: (BuildContext context) { - return CreateSubSpaceModelDialog( - isEdit: true, - dialogTitle: 'Edit Sub-space', - existingSubSpaces: subspaces, - allTags: allTags, - spaceName: spaceNameController.text, - spaceTagModels: spaceTagModels, - products: products, - spaceModel: spaceModel, - onLoad: onLoad, - ); - }, - ); + await _openDialog(context, 'Edit Sub-space'); }, child: Chip( label: const Text( @@ -137,4 +86,21 @@ class SubspaceModelCreate extends StatelessWidget { ), ); } + + Future _openDialog(BuildContext context, String dialogTitle) async { + await showDialog( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return CreateSubSpaceModelDialog( + isEdit: true, + dialogTitle: dialogTitle, + existingSubSpaces: subspaces, + onUpdate: (subspaceModels) { + onSpaceModelUpdate!(subspaceModels); + }, + ); + }, + ); + } } From a7256c8d5d8ce8e0a68659284f1e3acf936270a5 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 15 Jan 2025 11:29:19 +0400 Subject: [PATCH 097/175] updated duplication ui --- .../bloc/subspace_model_bloc.dart | 36 ++++- .../bloc/subspace_model_state.dart | 4 + .../views/create_subspace_model_dialog.dart | 137 +++++++++++------- 3 files changed, 116 insertions(+), 61 deletions(-) diff --git a/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart index 6c12ad04..1e8d0ddc 100644 --- a/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart +++ b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart @@ -6,19 +6,23 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_ import 'package:syncrow_web/utils/constants/action_enum.dart'; class SubSpaceModelBloc extends Bloc { - SubSpaceModelBloc() : super(SubSpaceModelState([], [], '')) { + SubSpaceModelBloc() : super(SubSpaceModelState([], [], '', {})) { // Handle AddSubSpaceModel Event on((event, emit) { - // Check for duplicate names (case-insensitive) final existingNames = state.subSpaces.map((e) => e.subspaceName.toLowerCase()).toSet(); if (existingNames.contains(event.subSpace.subspaceName.toLowerCase())) { - // Emit state with an error message if duplicate name exists + final updatedDuplicates = Set.from(state.duplicates) + ..add(event.subSpace.subspaceName.toLowerCase()); + final updatedSubSpaces = + List.from(state.subSpaces) + ..add(event.subSpace); emit(SubSpaceModelState( - state.subSpaces, + updatedSubSpaces, state.updatedSubSpaceModels, - 'Subspace name already exists.', + '*Duplicated sub-space name', + updatedDuplicates, )); } else { // Add subspace if no duplicate exists @@ -29,7 +33,9 @@ class SubSpaceModelBloc extends Bloc { emit(SubSpaceModelState( updatedSubSpaces, state.updatedSubSpaceModels, - '', // Clear error message + '', + state.duplicates, +// Clear error message )); } }); @@ -42,6 +48,16 @@ class SubSpaceModelBloc extends Bloc { final updatedSubspaceModels = List.from( state.updatedSubSpaceModels, ); + final nameOccurrences = {}; + for (final subSpace in updatedSubSpaces) { + final lowerName = subSpace.subspaceName.toLowerCase(); + nameOccurrences[lowerName] = (nameOccurrences[lowerName] ?? 0) + 1; + } + + final updatedDuplicates = nameOccurrences.entries + .where((entry) => entry.value > 1) + .map((entry) => entry.key) + .toSet(); if (event.subSpace.uuid?.isNotEmpty ?? false) { updatedSubspaceModels.add(UpdateSubspaceTemplateModel( @@ -53,7 +69,9 @@ class SubSpaceModelBloc extends Bloc { emit(SubSpaceModelState( updatedSubSpaces, updatedSubspaceModels, - '', // Clear error message + '', + updatedDuplicates, +// Clear error message )); }); @@ -78,7 +96,9 @@ class SubSpaceModelBloc extends Bloc { emit(SubSpaceModelState( updatedSubSpaces, updatedSubspaceModels, - '', // Clear error message + '', + state.duplicates, +// Clear error message )); }); } diff --git a/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart index ab60a813..207d9601 100644 --- a/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart +++ b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart @@ -5,22 +5,26 @@ class SubSpaceModelState { final List subSpaces; final List updatedSubSpaceModels; final String errorMessage; + final Set duplicates; SubSpaceModelState( this.subSpaces, this.updatedSubSpaceModels, this.errorMessage, + this.duplicates, ); SubSpaceModelState copyWith({ List? subSpaces, List? updatedSubSpaceModels, String? errorMessage, + Set? duplicates, }) { return SubSpaceModelState( subSpaces ?? this.subSpaces, updatedSubSpaceModels ?? this.updatedSubSpaceModels, errorMessage ?? this.errorMessage, + duplicates ?? this.duplicates, ); } } 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 29ed3846..82aa3684 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 @@ -46,7 +46,7 @@ class CreateSubSpaceModelDialog extends StatelessWidget { return Container( color: ColorsManager.whiteColors, child: SizedBox( - width: screenWidth * 0.35, + width: screenWidth * 0.3, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( @@ -73,41 +73,64 @@ class CreateSubSpaceModelDialog extends StatelessWidget { spacing: 8.0, runSpacing: 8.0, children: [ - ...state.subSpaces.map( - (subSpace) => Chip( - label: Text( - subSpace.subspaceName, - style: const TextStyle( - color: ColorsManager.spaceColor), - ), - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: const BorderSide( - color: ColorsManager.transparentColor, - width: 0, - ), - ), - deleteIcon: Container( - width: 24, - height: 24, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: ColorsManager.lightGrayColor, - width: 1.5, + ...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: const TextStyle( + color: ColorsManager.spaceColor, ), ), - child: const Icon( - Icons.close, - size: 16, - color: ColorsManager.lightGrayColor, + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + side: BorderSide( + color: isDuplicate + ? ColorsManager.red + : ColorsManager.transparentColor, + width: 0, + ), ), - ), - onDeleted: () => context - .read() - .add(RemoveSubSpaceModel(subSpace)), - ), + 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, @@ -135,20 +158,20 @@ class CreateSubSpaceModelDialog extends StatelessWidget { color: ColorsManager.blackColor), ), ), - if (state.errorMessage.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - state.errorMessage, - style: const TextStyle( - color: ColorsManager.warningRed, - fontSize: 12, - ), - ), - ), ], ), ), + if (state.errorMessage.isNotEmpty) + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: Text( + state.errorMessage, + style: const TextStyle( + color: ColorsManager.red, + fontSize: 12, + ), + ), + ), const SizedBox(height: 16), Row( children: [ @@ -163,17 +186,25 @@ class CreateSubSpaceModelDialog extends StatelessWidget { const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: () async { - final subSpaces = context - .read() - .state - .subSpaces; - Navigator.of(context).pop(); - onUpdate!(subSpaces); - }, + onPressed: (state.subSpaces.isEmpty || + 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: ColorsManager.whiteColors, + foregroundColor: state.subSpaces.isEmpty || + state.errorMessage.isNotEmpty + ? ColorsManager.whiteColorsWithOpacity + : ColorsManager.whiteColors, child: const Text('OK'), ), ), From c12c73f20a3467f4580605f982a8e982dc0e6029 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 16 Jan 2025 00:10:45 +0400 Subject: [PATCH 098/175] add close button color --- .../views/assign_tag_models_dialog.dart | 71 ++++++++++++------- 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 1b162832..7a287fb7 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -23,17 +23,17 @@ class AssignTagModelsDialog extends StatelessWidget { final String spaceName; final String title; - const AssignTagModelsDialog({ - Key? key, - required this.products, - required this.subspaces, - required this.addedProducts, - required this.initialTags, - this.onTagsAssigned, - this.allTags, - required this.spaceName, - required this.title - }) : super(key: key); + const AssignTagModelsDialog( + {Key? key, + required this.products, + required this.subspaces, + required this.addedProducts, + required this.initialTags, + this.onTagsAssigned, + this.allTags, + required this.spaceName, + required this.title}) + : super(key: key); @override Widget build(BuildContext context) { @@ -123,19 +123,38 @@ class AssignTagModelsDialog extends StatelessWidget { tag.product?.name ?? 'Unknown', overflow: TextOverflow.ellipsis, )), - IconButton( - icon: const Icon(Icons.close, - color: ColorsManager.warningRed, - size: 16), - onPressed: () { - context - .read() - .add(DeleteTagModel( - tagToDelete: tag, - tags: state.tags)); - }, - tooltip: 'Delete Tag', - ) + const SizedBox(width: 10), + Container( + width: 20.0, + height: 20.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: ColorsManager + .lightGrayColor, + width: 1.0, + ), + ), + child: IconButton( + icon: const Icon( + Icons.close, + color: ColorsManager + .lightGreyColor, + size: 16, + ), + onPressed: () { + context + .read() + .add(DeleteTagModel( + tagToDelete: tag, + tags: state.tags)); + }, + tooltip: 'Delete Tag', + padding: EdgeInsets.zero, + constraints: + const BoxConstraints(), + ), + ), ], ), ), @@ -154,7 +173,6 @@ class AssignTagModelsDialog extends StatelessWidget { )); }, decoration: const InputDecoration( - hintText: 'Enter Tag', border: InputBorder.none, ), style: const TextStyle( @@ -172,7 +190,8 @@ class AssignTagModelsDialog extends StatelessWidget { color: ColorsManager.whiteColors, icon: const Icon( Icons.arrow_drop_down, - color: ColorsManager.blackColor), + color: + ColorsManager.blackColor), onSelected: (value) { controller.text = value; context From 60028cdf786639acac55f5402b8abae16c4a53f1 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 16 Jan 2025 02:03:08 +0400 Subject: [PATCH 099/175] only appears the separator if there is both space model and product --- .../space_model/widgets/space_model_card_widget.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 ac8b49d0..323d7ac0 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 @@ -81,12 +81,12 @@ class SpaceModelCardWidget extends StatelessWidget { ), ), ), + if(productTagCount.isNotEmpty) Container( - width: 1.0, // Thickness of the line - color: ColorsManager.softGray, // Subtle grey color - margin: const EdgeInsets.symmetric( - vertical: 6.0), // Top and bottom spacing - ), // Right Container + width: 1.0, + color: ColorsManager.softGray, + margin: const EdgeInsets.symmetric(vertical: 6.0), + ), Expanded( flex: 1, // Distribute space proportionally child: Container( From 8a95f9355666b97d9bc51fd2f139591f8fa8bf86 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 16 Jan 2025 02:14:04 +0400 Subject: [PATCH 100/175] fixed list view --- lib/common/dialog_dropdown.dart | 138 +++++++++++++++ lib/common/dialog_textfield_dropdown.dart | 160 ++++++++++++++++++ .../bloc/assign_tag_model_bloc.dart | 3 +- .../views/assign_tag_models_dialog.dart | 158 +++++------------ 4 files changed, 344 insertions(+), 115 deletions(-) create mode 100644 lib/common/dialog_dropdown.dart create mode 100644 lib/common/dialog_textfield_dropdown.dart diff --git a/lib/common/dialog_dropdown.dart b/lib/common/dialog_dropdown.dart new file mode 100644 index 00000000..7274b3c0 --- /dev/null +++ b/lib/common/dialog_dropdown.dart @@ -0,0 +1,138 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DialogDropdown extends StatefulWidget { + final List items; + final ValueChanged onSelected; + final String? selectedValue; + + const DialogDropdown({ + Key? key, + required this.items, + required this.onSelected, + this.selectedValue, + }) : super(key: key); + + @override + _DialogDropdownState createState() => _DialogDropdownState(); +} + +class _DialogDropdownState extends State { + bool _isOpen = false; + late OverlayEntry _overlayEntry; + + @override + void initState() { + super.initState(); + } + + void _toggleDropdown() { + if (_isOpen) { + _closeDropdown(); + } else { + _openDropdown(); + } + } + + void _openDropdown() { + _overlayEntry = _createOverlayEntry(); + Overlay.of(context).insert(_overlayEntry); + _isOpen = true; + } + + void _closeDropdown() { + _overlayEntry.remove(); + _isOpen = false; + } + + OverlayEntry _createOverlayEntry() { + final renderBox = context.findRenderObject() as RenderBox; + final size = renderBox.size; + final offset = renderBox.localToGlobal(Offset.zero); + + return OverlayEntry( + builder: (context) { + return GestureDetector( + onTap: () { + _closeDropdown(); + }, + behavior: HitTestBehavior.translucent, + child: Stack( + children: [ + Positioned( + left: offset.dx, + top: offset.dy + size.height, + width: size.width, + child: Material( + elevation: 4.0, + child: Container( + color: ColorsManager.whiteColors, + constraints: const BoxConstraints( + maxHeight: 200.0, // Set max height for dropdown + ), + child: ListView.builder( + shrinkWrap: true, + itemCount: widget.items.length, + itemBuilder: (context, index) { + final item = widget.items[index]; + return Container( + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: ColorsManager.lightGrayBorderColor, + width: 1.0, + ), + ), + ), + child: ListTile( + title: Text( + item, + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + color: ColorsManager.textPrimaryColor, + ), + ), + onTap: () { + widget.onSelected(item); + _closeDropdown(); + }, + ), + ); + }, + ), + ), + ), + ), + ], + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: _toggleDropdown, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), + decoration: BoxDecoration( + border: Border.all(color: ColorsManager.transparentColor), + borderRadius: BorderRadius.circular(8.0), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.selectedValue ?? 'Select an item', + style: Theme.of(context).textTheme.bodyMedium, + ), + const Icon(Icons.arrow_drop_down), + ], + ), + ), + ); + } +} diff --git a/lib/common/dialog_textfield_dropdown.dart b/lib/common/dialog_textfield_dropdown.dart new file mode 100644 index 00000000..807f3417 --- /dev/null +++ b/lib/common/dialog_textfield_dropdown.dart @@ -0,0 +1,160 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DialogTextfieldDropdown extends StatefulWidget { + final List items; + final ValueChanged onSelected; + final String? initialValue; + + const DialogTextfieldDropdown({ + Key? key, + required this.items, + required this.onSelected, + this.initialValue, + }) : super(key: key); + + @override + _DialogTextfieldDropdownState createState() => + _DialogTextfieldDropdownState(); +} + +class _DialogTextfieldDropdownState extends State { + bool _isOpen = false; + late OverlayEntry _overlayEntry; + final TextEditingController _controller = TextEditingController(); + late List _filteredItems; // Filtered items list + + @override + void initState() { + super.initState(); + _controller.text = widget.initialValue ?? 'Select Tag'; + _filteredItems = List.from(widget.items); // Initialize filtered items + } + + void _toggleDropdown() { + if (_isOpen) { + _closeDropdown(); + } else { + _openDropdown(); + } + } + + void _openDropdown() { + _overlayEntry = _createOverlayEntry(); + Overlay.of(context).insert(_overlayEntry); + _isOpen = true; + } + + void _closeDropdown() { + _overlayEntry.remove(); + _isOpen = false; + } + + OverlayEntry _createOverlayEntry() { + final renderBox = context.findRenderObject() as RenderBox; + final size = renderBox.size; + final offset = renderBox.localToGlobal(Offset.zero); + + return OverlayEntry( + builder: (context) { + return GestureDetector( + onTap: () { + _closeDropdown(); + }, + behavior: HitTestBehavior.translucent, + child: Stack( + children: [ + Positioned( + left: offset.dx, + top: offset.dy + size.height, + width: size.width, + child: Material( + elevation: 4.0, + child: Container( + color: ColorsManager.whiteColors, + constraints: const BoxConstraints( + maxHeight: 200.0, + ), + child: ListView.builder( + shrinkWrap: true, + itemCount: _filteredItems.length, + itemBuilder: (context, index) { + final item = _filteredItems[index]; + return Container( + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: ColorsManager.lightGrayBorderColor, + width: 1.0, + ), + ), + ), + child: ListTile( + title: Text(item, + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + color: ColorsManager.textPrimaryColor)), + onTap: () { + _controller.text = item; + widget.onSelected(item); + setState(() { + _filteredItems + .remove(item); // Remove selected item + }); + _closeDropdown(); + }, + ), + ); + }, + ), + ), + ), + ), + ], + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: _toggleDropdown, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), + decoration: BoxDecoration( + border: Border.all(color: ColorsManager.transparentColor), + borderRadius: BorderRadius.circular(8.0), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: TextFormField( + controller: _controller, + onChanged: (value) { + setState(() { + _filteredItems = widget.items + .where((item) => + item.toLowerCase().contains(value.toLowerCase())) + .toList(); // Filter items dynamically + }); + widget.onSelected(value); + }, + style: Theme.of(context).textTheme.bodyMedium, + decoration: const InputDecoration( + hintText: 'Enter or Select tag', + border: InputBorder.none, + ), + ), + ), + const Icon(Icons.arrow_drop_down), + ], + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart index 3dd5e27d..ce4a38c2 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart @@ -127,7 +127,8 @@ class AssignTagModelBloc } final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); - return uniqueTags.length == tags.length && !hasEmptyTag; + final isValid = uniqueTags.length == tags.length && !hasEmptyTag; + return isValid; } String? _getValidationError(List tags) { diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 7a287fb7..3712f170 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/common/dialog_dropdown.dart'; +import 'package:syncrow_web/common/dialog_textfield_dropdown.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/all_spaces/model/product_model.dart'; @@ -79,6 +81,8 @@ class AssignTagModelsDialog extends StatelessWidget { style: Theme.of(context).textTheme.bodyMedium)), DataColumn( + numeric: false, + headingRowAlignment: MainAxisAlignment.start, label: Text('Tag', style: Theme.of(context).textTheme.bodyMedium)), @@ -109,6 +113,8 @@ class AssignTagModelsDialog extends StatelessWidget { : List.generate(state.tags.length, (index) { final tag = state.tags[index]; final controller = controllers[index]; + final availableTags = getAvailableTags( + allTags ?? [], state.tags, tag); return DataRow( cells: [ @@ -159,130 +165,43 @@ class AssignTagModelsDialog extends StatelessWidget { ), ), DataCell( - Row( - children: [ - Expanded( - child: TextFormField( - controller: controller, - onChanged: (value) { - context - .read() - .add(UpdateTag( - index: index, - tag: value.trim(), - )); - }, - decoration: const InputDecoration( - border: InputBorder.none, - ), - style: const TextStyle( - fontSize: 14, - color: ColorsManager.blackColor, - ), - ), + Container( + alignment: Alignment + .centerLeft, // Align cell content to the left + child: SizedBox( + width: double + .infinity, // Ensure full width for dropdown + child: DialogTextfieldDropdown( + items: availableTags ?? [], + onSelected: (value) { + controller.text = value; + context + .read() + .add(UpdateTag( + index: index, + tag: value, + )); + }, ), - SizedBox( - width: MediaQuery.of(context) - .size - .width * - 0.15, - child: PopupMenuButton( - color: ColorsManager.whiteColors, - icon: const Icon( - Icons.arrow_drop_down, - color: - ColorsManager.blackColor), - onSelected: (value) { - controller.text = value; - context - .read() - .add(UpdateTag( - index: index, - tag: value, - )); - }, - itemBuilder: (context) { - return (allTags ?? []) - .where((tagValue) => !state - .tags - .map((e) => e.tag) - .contains(tagValue)) - .map((tagValue) { - return PopupMenuItem( - textStyle: const TextStyle( - color: ColorsManager - .textPrimaryColor), - value: tagValue, - child: ConstrainedBox( - constraints: - BoxConstraints( - minWidth: MediaQuery.of( - context) - .size - .width * - 0.15, - maxWidth: MediaQuery.of( - context) - .size - .width * - 0.15, - ), - child: Text( - tagValue, - overflow: TextOverflow - .ellipsis, - ), - )); - }).toList(); - }, - ), - ), - ], + ), ), ), DataCell( - DropdownButtonHideUnderline( - child: DropdownButton( - value: tag.location ?? 'None', - dropdownColor: ColorsManager - .whiteColors, // Dropdown background - style: const TextStyle( - color: Colors - .black), // Style for selected text - items: [ - const DropdownMenuItem( - value: 'None', - child: Text( - 'None', - style: TextStyle( - color: ColorsManager - .textPrimaryColor), - ), - ), - ...locations.map((location) { - return DropdownMenuItem( - value: location, - child: Text( - location, - style: const TextStyle( - color: ColorsManager - .textPrimaryColor), - ), - ); - }).toList(), - ], - onChanged: (value) { - if (value != null) { + SizedBox( + width: double.infinity, + child: DialogDropdown( + items: locations, + selectedValue: + tag.location ?? 'None', + onSelected: (value) { context .read() .add(UpdateLocation( index: index, location: value, )); - } - }, - ), - ), + }, + )), ), ], ); @@ -380,4 +299,15 @@ class AssignTagModelsDialog extends StatelessWidget { ), ); } + + List getAvailableTags( + List allTags, List currentTags, TagModel currentTag) { + print("happening"); + return allTags + .where((tagValue) => !currentTags + .where((e) => e != currentTag) // Exclude the current row + .map((e) => e.tag) + .contains(tagValue)) + .toList(); + } } From 9706c2655c9a6306723e30be874a268559872ee2 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 16 Jan 2025 02:15:24 +0400 Subject: [PATCH 101/175] added color --- .../assign_tag_models/views/assign_tag_models_dialog.dart | 1 - lib/utils/color_manager.dart | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 3712f170..b84c32e5 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -302,7 +302,6 @@ class AssignTagModelsDialog extends StatelessWidget { List getAvailableTags( List allTags, List currentTags, TagModel currentTag) { - print("happening"); return allTags .where((tagValue) => !currentTags .where((e) => e != currentTag) // Exclude the current row diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index d1d111dd..301365ed 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -68,6 +68,7 @@ abstract class ColorsManager { static const Color disabledRedText = Color(0xFF890002); static const Color invitedOrange = Color(0xFFFFE193); static const Color invitedOrangeText = Color(0xFFFFBF00); + static const Color lightGrayBorderColor = Color(0xB2D5D5D5); //background: #F8F8F8; } From bae5ae17a731089ae60d168fb47f1e895af798e8 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 16 Jan 2025 10:17:57 +0400 Subject: [PATCH 102/175] updated the condition --- .../widgets/space_model_card_widget.dart | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) 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 323d7ac0..df0fba4f 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 @@ -2,7 +2,6 @@ import 'package:flutter/material.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/dynamic_product_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/widgets/room_name_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class SpaceModelCardWidget extends StatelessWidget { @@ -81,12 +80,12 @@ class SpaceModelCardWidget extends StatelessWidget { ), ), ), - if(productTagCount.isNotEmpty) - Container( - width: 1.0, - color: ColorsManager.softGray, - margin: const EdgeInsets.symmetric(vertical: 6.0), - ), + if (productTagCount.isNotEmpty && model.subspaceModels != null) + Container( + width: 1.0, + color: ColorsManager.softGray, + margin: const EdgeInsets.symmetric(vertical: 6.0), + ), Expanded( flex: 1, // Distribute space proportionally child: Container( From 145086b9de4b7254a370deb9104aecd5dd628fa9 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 17 Jan 2025 12:40:17 +0400 Subject: [PATCH 103/175] updated grid view --- .../tag_model/bloc/add_device_model_bloc.dart | 85 +++++--- .../bloc/add_device_model_state.dart | 36 ++++ .../bloc/add_device_type_model_event.dart | 19 ++ .../views/add_device_type_model_widget.dart | 196 ++++++++++-------- .../widgets/scrollable_grid_view_widget.dart | 9 +- 5 files changed, 231 insertions(+), 114 deletions(-) create mode 100644 lib/pages/spaces_management/tag_model/bloc/add_device_model_state.dart diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart index 3091d152..9c617a12 100644 --- a/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart @@ -1,38 +1,75 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart'; class AddDeviceTypeModelBloc - extends Bloc> { - AddDeviceTypeModelBloc(List initialProducts) - : super(initialProducts) { + extends Bloc { + AddDeviceTypeModelBloc() : super(AddDeviceModelInitial()) { + on(_onInitializeTagModels); on(_onUpdateProductCount); } - void _onUpdateProductCount( - UpdateProductCountEvent event, Emitter> emit) { - final existingProduct = state.firstWhere( - (p) => p.productId == event.productId, - orElse: () => SelectedProduct(productId: event.productId, count: 0,productName: event.productName,product: event.product ), - ); + void _onInitializeTagModels( + InitializeDeviceTypeModel event, Emitter emit) { + emit(AddDeviceModelLoaded( + selectedProducts: event.addedProducts, + initialTag: event.initialTags, + )); + } - if (event.count > 0) { - if (!state.contains(existingProduct)) { - emit([ - ...state, - SelectedProduct(productId: event.productId, count: event.count, productName: event.productName, product: event.product) - ]); + void _onUpdateProductCount( + UpdateProductCountEvent event, Emitter emit) { + final currentState = state; + + if (currentState is AddDeviceModelLoaded) { + final existingProduct = currentState.selectedProducts.firstWhere( + (p) => p.productId == event.productId, + orElse: () => SelectedProduct( + productId: event.productId, + count: 0, + productName: event.productName, + product: event.product, + ), + ); + + List updatedProducts; + + if (event.count > 0) { + if (!currentState.selectedProducts.contains(existingProduct)) { + updatedProducts = [ + ...currentState.selectedProducts, + SelectedProduct( + productId: event.productId, + count: event.count, + productName: event.productName, + product: event.product, + ), + ]; + } else { + updatedProducts = currentState.selectedProducts.map((p) { + if (p.productId == event.productId) { + return SelectedProduct( + productId: p.productId, + count: event.count, + productName: p.productName, + product: p.product, + ); + } + return p; + }).toList(); + } } else { - final updatedList = state.map((p) { - if (p.productId == event.productId) { - return SelectedProduct(productId: p.productId, count: event.count, productName: p.productName,product: p.product); - } - return p; - }).toList(); - emit(updatedList); + // Remove the product if the count is 0 + updatedProducts = currentState.selectedProducts + .where((p) => p.productId != event.productId) + .toList(); } - } else { - emit(state.where((p) => p.productId != event.productId).toList()); + + // Emit the updated state + emit(AddDeviceModelLoaded( + selectedProducts: updatedProducts, + initialTag: currentState.initialTag)); } } } diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_model_state.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_model_state.dart new file mode 100644 index 00000000..f45471cd --- /dev/null +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_model_state.dart @@ -0,0 +1,36 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; + +abstract class AddDeviceModelState extends Equatable { + const AddDeviceModelState(); + + @override + List get props => []; +} + +class AddDeviceModelInitial extends AddDeviceModelState {} + +class AddDeviceModelLoading extends AddDeviceModelState {} + +class AddDeviceModelLoaded extends AddDeviceModelState { + final List selectedProducts; + final List initialTag; + + const AddDeviceModelLoaded({ + required this.selectedProducts, + required this.initialTag, + }); + + @override + List get props => [selectedProducts, initialTag]; +} + +class AddDeviceModelError extends AddDeviceModelState { + final String errorMessage; + + const AddDeviceModelError(this.errorMessage); + + @override + List get props => [errorMessage]; +} diff --git a/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart index 1d6976f8..9b3a8b1e 100644 --- a/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart +++ b/lib/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart @@ -1,11 +1,16 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; abstract class AddDeviceTypeModelEvent extends Equatable { + const AddDeviceTypeModelEvent(); + @override List get props => []; } + class UpdateProductCountEvent extends AddDeviceTypeModelEvent { final String productId; final int count; @@ -17,3 +22,17 @@ class UpdateProductCountEvent extends AddDeviceTypeModelEvent { @override List get props => [productId, count]; } + + +class InitializeDeviceTypeModel extends AddDeviceTypeModelEvent { + final List initialTags; + final List addedProducts; + + const InitializeDeviceTypeModel({ + this.initialTags = const [], + required this.addedProducts, + }); + + @override + List get props => [initialTags, addedProducts]; +} diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index f7f4c3f7..ea0a94a6 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -9,6 +9,8 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_ import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -21,14 +23,15 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final List? allTags; final String spaceName; - const AddDeviceTypeModelWidget( - {super.key, - this.products, - this.initialSelectedProducts, - this.subspaces, - this.allTags, - this.spaceTagModels, - required this.spaceName}); + const AddDeviceTypeModelWidget({ + super.key, + this.products, + this.initialSelectedProducts, + this.subspaces, + this.allTags, + this.spaceTagModels, + required this.spaceName, + }); @override Widget build(BuildContext context) { @@ -40,91 +43,108 @@ class AddDeviceTypeModelWidget extends StatelessWidget { : 3; return BlocProvider( - create: (_) => AddDeviceTypeModelBloc(initialSelectedProducts ?? []), - child: Builder( - builder: (context) => AlertDialog( - title: const Text('Add Devices'), - backgroundColor: ColorsManager.whiteColors, - content: SingleChildScrollView( - child: Container( - width: size.width * 0.9, - height: size.height * 0.65, - color: ColorsManager.textFieldGreyColor, - child: Column( - children: [ - const SizedBox(height: 16), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: ScrollableGridViewWidget( - products: products, crossAxisCount: crossAxisCount), - ), + create: (_) => AddDeviceTypeModelBloc() + ..add(InitializeDeviceTypeModel( + initialTags: spaceTagModels ?? [], + addedProducts: initialSelectedProducts ?? [], + )), + child: Builder( + builder: (context) => AlertDialog( + title: const Text('Add Devices'), + backgroundColor: ColorsManager.whiteColors, + content: BlocBuilder( + builder: (context, state) { + if (state is AddDeviceModelLoading) { + return const Center(child: CircularProgressIndicator()); + } + if (state is AddDeviceModelLoaded) { + return SingleChildScrollView( + child: Container( + width: size.width * 0.9, + height: size.height * 0.65, + color: ColorsManager.textFieldGreyColor, + child: Column( + children: [ + const SizedBox(height: 16), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: ScrollableGridViewWidget( + products: products, + crossAxisCount: crossAxisCount, + initialProductCounts: state.selectedProducts, + ), + ), + ), + ], ), - ], + ), + ); + } + return const SizedBox(); + }, + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CancelButton( + label: 'Cancel', + onPressed: () async { + Navigator.of(context).pop(); + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => CreateSpaceModelDialog( + products: products, + allTags: allTags, + spaceModel: SpaceTemplateModel( + modelName: spaceName, + subspaceModels: subspaces, + tags: spaceTagModels, + ), + ), + ); + }, ), - ), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CancelButton( - label: 'Cancel', - onPressed: () async { - Navigator.of(context).pop(); - await showDialog( + ActionButton( + label: 'Continue', + backgroundColor: ColorsManager.secondaryColor, + foregroundColor: ColorsManager.whiteColors, + onPressed: () async { + final state = context.read().state; + if (state is AddDeviceModelLoaded && + state.selectedProducts.isNotEmpty) { + final initialTags = generateInitialTags( + spaceTagModels: spaceTagModels, + subspaces: subspaces, + ); + + final dialogTitle = initialTags.isNotEmpty + ? 'Edit Device' + : 'Assign Tags'; + await showDialog( barrierDismissible: false, context: context, - builder: (context) => CreateSpaceModelDialog( - products: products, - allTags: allTags, - spaceModel: SpaceTemplateModel( - modelName: spaceName, - subspaceModels: subspaces, - tags: spaceTagModels, - )), - ); - }, - ), - ActionButton( - label: 'Continue', - backgroundColor: ColorsManager.secondaryColor, - foregroundColor: ColorsManager.whiteColors, - onPressed: () async { - final currentState = - context.read().state; - Navigator.of(context).pop(); - - if (currentState.isNotEmpty) { - final initialTags = generateInitialTags( - spaceTagModels: spaceTagModels, + builder: (context) => AssignTagModelsDialog( + products: products, subspaces: subspaces, - ); - - final dialogTitle = initialTags.isNotEmpty - ? 'Edit Device' - : 'Assign Tags'; - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => AssignTagModelsDialog( - products: products, - subspaces: subspaces, - addedProducts: currentState, - allTags: allTags, - spaceName: spaceName, - initialTags: initialTags, - title: dialogTitle, - ), - ); - } - }, - ), - ], - ), - ], - ), - )); + addedProducts: state.selectedProducts, + allTags: allTags, + spaceName: spaceName, + initialTags: state.initialTag, + title: dialogTitle, + ), + ); + } + }, + ), + ], + ), + ], + ), + ), + ); } List generateInitialTags({ diff --git a/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart b/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart index 2a653dde..3e32ccd8 100644 --- a/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart +++ b/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart'; class ScrollableGridViewWidget extends StatelessWidget { @@ -24,8 +25,12 @@ class ScrollableGridViewWidget extends StatelessWidget { return Scrollbar( controller: scrollController, thumbVisibility: true, - child: BlocBuilder>( - builder: (context, productCounts) { + child: BlocBuilder( + builder: (context, state) { + final productCounts = state is AddDeviceModelLoaded + ? state.selectedProducts + : []; + return GridView.builder( controller: scrollController, shrinkWrap: true, From 7109421358fae255e0d7477c2510f027302c5c04 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 17 Jan 2025 23:51:56 +0400 Subject: [PATCH 104/175] fixed first time flow --- .../widgets/tag_chips_display_widget.dart | 4 +-- .../views/add_device_type_model_widget.dart | 25 ++++++++----------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 799eb71b..21283ca4 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -88,6 +88,7 @@ class TagChipDisplay extends StatelessWidget { barrierDismissible: false, context: context, builder: (context) => AddDeviceTypeModelWidget( + isCreate: false, products: products, subspaces: subspaces, allTags: allTags, @@ -121,8 +122,6 @@ class TagChipDisplay extends StatelessWidget { ) : TextButton( onPressed: () async { - Navigator.of(context).pop(); - final result = await showDialog( barrierDismissible: false, context: context, @@ -131,6 +130,7 @@ class TagChipDisplay extends StatelessWidget { subspaces: subspaces, allTags: allTags, spaceName: spaceNameController.text, + isCreate: true, ), ); if (result == true) {} diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index ea0a94a6..dbc4d6ea 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -22,6 +22,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final List? spaceTagModels; final List? allTags; final String spaceName; + final bool isCreate; const AddDeviceTypeModelWidget({ super.key, @@ -31,6 +32,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { this.allTags, this.spaceTagModels, required this.spaceName, + required this.isCreate, }); @override @@ -68,7 +70,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { const SizedBox(height: 16), Expanded( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), + padding: + const EdgeInsets.symmetric(horizontal: 20.0), child: ScrollableGridViewWidget( products: products, crossAxisCount: crossAxisCount, @@ -91,20 +94,9 @@ class AddDeviceTypeModelWidget extends StatelessWidget { CancelButton( label: 'Cancel', onPressed: () async { - Navigator.of(context).pop(); - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => CreateSpaceModelDialog( - products: products, - allTags: allTags, - spaceModel: SpaceTemplateModel( - modelName: spaceName, - subspaceModels: subspaces, - tags: spaceTagModels, - ), - ), - ); + if (isCreate) { + Navigator.of(context).pop(); + } }, ), ActionButton( @@ -119,6 +111,9 @@ class AddDeviceTypeModelWidget extends StatelessWidget { spaceTagModels: spaceTagModels, subspaces: subspaces, ); + if (isCreate) { + Navigator.of(context).pop(); + } final dialogTitle = initialTags.isNotEmpty ? 'Edit Device' From ec5b7d4395ccc4e0570e674a43ba682e78b49d4d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 17 Jan 2025 23:53:43 +0400 Subject: [PATCH 105/175] changed button name --- .../views/assign_tag_models_dialog.dart | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index b84c32e5..d4015a96 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -223,21 +223,9 @@ class AssignTagModelsDialog extends StatelessWidget { const SizedBox(width: 10), Expanded( child: CancelButton( - label: 'Cancel', + label: 'Add New Device', onPressed: () async { Navigator.of(context).pop(); - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => CreateSpaceModelDialog( - products: products, - allTags: allTags, - spaceModel: SpaceTemplateModel( - modelName: spaceName, - subspaceModels: subspaces, - tags: initialTags), - ), - ); }, ), ), From 440263e2f94b40c64ca6c21d8d236d3501676e7e Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sat, 18 Jan 2025 00:32:48 +0400 Subject: [PATCH 106/175] added initial flow --- .../views/assign_tag_models_dialog.dart | 35 +++--- .../bloc/create_space_model_bloc.dart | 18 ++++ .../bloc/create_space_model_event.dart | 8 ++ .../models/space_template_model.dart | 2 +- .../dialog/create_space_model_dialog.dart | 29 +++-- .../widgets/tag_chips_display_widget.dart | 42 ++++---- .../views/add_device_type_model_widget.dart | 101 +++++++++++------- 7 files changed, 152 insertions(+), 83 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index d4015a96..e6242c5e 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -9,10 +9,8 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_pr import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_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/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class AssignTagModelsDialog extends StatelessWidget { @@ -24,6 +22,8 @@ class AssignTagModelsDialog extends StatelessWidget { final List? allTags; final String spaceName; final String title; + final void Function( + List? tags, List? subspaces)? onUpdate; const AssignTagModelsDialog( {Key? key, @@ -34,7 +34,8 @@ class AssignTagModelsDialog extends StatelessWidget { this.onTagsAssigned, this.allTags, required this.spaceName, - required this.title}) + required this.title, + this.onUpdate}) : super(key: key); @override @@ -226,6 +227,21 @@ class AssignTagModelsDialog extends StatelessWidget { label: 'Add New Device', onPressed: () async { Navigator.of(context).pop(); + final assignedTags = {}; + for (var tag in state.tags) { + if (tag.location == null || subspaces == null) { + continue; + } + for (var subspace in subspaces!) { + if (tag.location == subspace.subspaceName) { + subspace.tags ??= []; + subspace.tags!.add(tag); + assignedTags.add(tag); + break; + } + } + } + state.tags.removeWhere(assignedTags.contains); }, ), ), @@ -256,18 +272,7 @@ class AssignTagModelsDialog extends StatelessWidget { } } state.tags.removeWhere(assignedTags.contains); - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => CreateSpaceModelDialog( - products: products, - allTags: allTags, - spaceModel: SpaceTemplateModel( - modelName: spaceName, - subspaceModels: subspaces, - tags: state.tags), - ), - ); + onUpdate!(state.tags,subspaces); } : null, child: const Text('Save'), diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 8055e217..ec17482f 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -84,6 +84,24 @@ class CreateSpaceModelBloc } }); + + + on((event, emit) { + final currentState = state; + + if (currentState is CreateSpaceModelLoaded) { + final updatedTags = currentState.space.copyWith( + tags: [ + ...(_space!.tags ?? []), + ...event.tags, + ], + ); + emit(CreateSpaceModelLoaded(updatedTags)); + } else { + emit(CreateSpaceModelError("Space template not initialized")); + } + }); + on((event, emit) { final currentState = state; if (currentState is CreateSpaceModelLoaded) { diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index 1d7f6012..2bcb12b6 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -1,6 +1,7 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; abstract class CreateSpaceModelEvent extends Equatable { const CreateSpaceModelEvent(); @@ -46,6 +47,13 @@ class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent { AddSubspacesToSpaceTemplate(this.subspaces); } +class AddTagsToSpaceTemplate extends CreateSpaceModelEvent { + final List tags; + + AddTagsToSpaceTemplate(this.tags); +} + + class ValidateSpaceTemplateName extends CreateSpaceModelEvent { final String name; 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 4f762c9a..84f568a5 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 @@ -13,7 +13,7 @@ class SpaceTemplateModel extends Equatable { String internalId; @override - List get props => [modelName, subspaceModels]; + List get props => [modelName, subspaceModels, tags]; SpaceTemplateModel({ this.uuid, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 98c82f2a..1e2fc517 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -118,13 +118,28 @@ class CreateSpaceModelDialog extends StatelessWidget { }, ), const SizedBox(height: 10), - TagChipDisplay(context, - screenWidth: screenWidth, - spaceModel: updatedSpaceModel, - products: products, - subspaces: subspaces, - allTags: allTags, - spaceNameController: spaceNameController), + TagChipDisplay( + context, + screenWidth: screenWidth, + spaceModel: updatedSpaceModel, + products: products, + subspaces: subspaces, + allTags: allTags, + spaceNameController: spaceNameController, + onLoad: (tags, subspaces) { + if(subspaces!=null){ + context + .read() + .add(AddSubspacesToSpaceTemplate(subspaces)); + } + if(tags!=null){ + context + .read() + .add(AddTagsToSpaceTemplate(tags)); + + } + }, + ), const SizedBox(height: 20), SizedBox( width: screenWidth * 0.25, diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 21283ca4..d83db22a 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -16,17 +16,19 @@ class TagChipDisplay extends StatelessWidget { final List? subspaces; final List? allTags; final TextEditingController spaceNameController; + final void Function( + List? tags, List? subspaces)? onLoad; - const TagChipDisplay( - BuildContext context, { - Key? key, - required this.screenWidth, - required this.spaceModel, - required this.products, - required this.subspaces, - required this.allTags, - required this.spaceNameController, - }) : super(key: key); + const TagChipDisplay(BuildContext context, + {Key? key, + required this.screenWidth, + required this.spaceModel, + required this.products, + required this.subspaces, + required this.allTags, + required this.spaceNameController, + this.onLoad}) + : super(key: key); @override Widget build(BuildContext context) { @@ -104,15 +106,12 @@ class TagChipDisplay extends StatelessWidget { child: Chip( label: const Text( 'Edit', - style: TextStyle( - color: ColorsManager.spaceColor), + style: TextStyle(color: ColorsManager.spaceColor), ), - backgroundColor: - ColorsManager.whiteColors, + backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: const BorderSide( - color: ColorsManager.spaceColor), + borderRadius: BorderRadius.circular(16), + side: const BorderSide(color: ColorsManager.spaceColor), ), ), ), @@ -122,7 +121,7 @@ class TagChipDisplay extends StatelessWidget { ) : TextButton( onPressed: () async { - final result = await showDialog( + await showDialog( barrierDismissible: false, context: context, builder: (context) => AddDeviceTypeModelWidget( @@ -131,9 +130,13 @@ class TagChipDisplay extends StatelessWidget { allTags: allTags, spaceName: spaceNameController.text, isCreate: true, + onLoad: (tags, subspaces) { + if (onLoad != null) { + onLoad!(tags, subspaces); + } + }, ), ); - if (result == true) {} }, style: TextButton.styleFrom( padding: EdgeInsets.zero, @@ -189,5 +192,4 @@ class TagChipDisplay extends StatelessWidget { )) .toList(); } - } diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index dbc4d6ea..8ed903b1 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -1,17 +1,15 @@ 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/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart'; -import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -23,6 +21,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final List? allTags; final String spaceName; final bool isCreate; + final void Function( + List? tags, List? subspaces)? onLoad; const AddDeviceTypeModelWidget({ super.key, @@ -33,6 +33,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { this.spaceTagModels, required this.spaceName, required this.isCreate, + this.onLoad, }); @override @@ -94,46 +95,66 @@ class AddDeviceTypeModelWidget extends StatelessWidget { CancelButton( label: 'Cancel', onPressed: () async { - if (isCreate) { - Navigator.of(context).pop(); - } - }, - ), - ActionButton( - label: 'Continue', - backgroundColor: ColorsManager.secondaryColor, - foregroundColor: ColorsManager.whiteColors, - onPressed: () async { - final state = context.read().state; - if (state is AddDeviceModelLoaded && - state.selectedProducts.isNotEmpty) { - final initialTags = generateInitialTags( - spaceTagModels: spaceTagModels, - subspaces: subspaces, - ); - if (isCreate) { - Navigator.of(context).pop(); - } - - final dialogTitle = initialTags.isNotEmpty - ? 'Edit Device' - : 'Assign Tags'; - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => AssignTagModelsDialog( - products: products, - subspaces: subspaces, - addedProducts: state.selectedProducts, - allTags: allTags, - spaceName: spaceName, - initialTags: state.initialTag, - title: dialogTitle, - ), - ); + if (isCreate) { + Navigator.of(context).pop(); } }, ), + SizedBox( + width: 140, + child: + BlocBuilder( + builder: (context, state) { + final isDisabled = state is AddDeviceModelLoaded && + state.selectedProducts.isEmpty; + + return DefaultButton( + backgroundColor: ColorsManager.secondaryColor, + foregroundColor: isDisabled + ? ColorsManager.whiteColorsWithOpacity + : ColorsManager.whiteColors, + borderRadius: 10, + onPressed: isDisabled + ? null // Disable the button + : () async { + if (state is AddDeviceModelLoaded && + state.selectedProducts.isNotEmpty) { + final initialTags = generateInitialTags( + spaceTagModels: spaceTagModels, + subspaces: subspaces, + ); + if (isCreate) { + Navigator.of(context).pop(); + } + + final dialogTitle = initialTags.isNotEmpty + ? 'Edit Device' + : 'Assign Tags'; + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AssignTagModelsDialog( + products: products, + subspaces: subspaces, + addedProducts: state.selectedProducts, + allTags: allTags, + spaceName: spaceName, + initialTags: state.initialTag, + title: dialogTitle, + onUpdate: (tags, subspaces) { + if (onLoad != null) { + onLoad!(tags, subspaces); + } + }, + ), + ); + } + }, + child: const Text('Next'), + ); + }, + ), + ), ], ), ], From fe680d15f20a893748db580fd81dd35752b73213 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 20 Jan 2025 10:28:46 +0400 Subject: [PATCH 107/175] edit and create --- .../space_model/view/space_model_page.dart | 30 ++++++++++++++----- .../dialog/create_space_model_dialog.dart | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index eab43e08..d0b5a300 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -67,10 +67,24 @@ class SpaceModelPage extends StatelessWidget { } // Render existing space model final model = spaceModels[index]; - return Container( - margin: const EdgeInsets.all(8.0), - child: SpaceModelCardWidget(model:model), - ); + return GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return CreateSpaceModelDialog( + products: products, + allTags: allTagValues, + spaceModel: model, + onLoad: (newModel) {}, + ); + }, + ); + }, + child: Container( + margin: const EdgeInsets.all(8.0), + child: SpaceModelCardWidget(model: model), + )); }, ), ), @@ -94,14 +108,14 @@ class SpaceModelPage extends StatelessWidget { double _calculateChildAspectRatio(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; if (screenWidth > 1600) { - return 2; + return 2; } if (screenWidth > 1200) { - return 3; + return 3; } else if (screenWidth > 800) { - return 3.5; + return 3.5; } else { - return 4.0; + return 4.0; } } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 1e2fc517..e5dea2b9 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -72,7 +72,7 @@ class CreateSpaceModelDialog extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - 'Create New Space Model', + spaceModel?.uuid == null ? 'Create New Space Model': 'Edit Space Model', style: Theme.of(context) .textTheme .headlineLarge From 2f6bd31aa2c65b7769fd24b033310ae69f35e185 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 20 Jan 2025 10:52:23 +0400 Subject: [PATCH 108/175] helper class --- .../spaces_management/helper/tag_helper.dart | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 lib/pages/spaces_management/helper/tag_helper.dart diff --git a/lib/pages/spaces_management/helper/tag_helper.dart b/lib/pages/spaces_management/helper/tag_helper.dart new file mode 100644 index 00000000..ed4525ae --- /dev/null +++ b/lib/pages/spaces_management/helper/tag_helper.dart @@ -0,0 +1,76 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; + +class TagHelper { + static List generateInitialTags({ + List? spaceTagModels, + List? subspaces, + }) { + final List initialTags = []; + + if (spaceTagModels != null) { + initialTags.addAll(spaceTagModels); + } + + if (subspaces != null) { + for (var subspace in subspaces) { + if (subspace.tags != null) { + initialTags.addAll( + subspace.tags!.map( + (tag) => tag.copyWith(location: subspace.subspaceName), + ), + ); + } + } + } + + return initialTags; + } + + static Map groupTags(List tags) { + final Map groupedTags = {}; + for (var tag in tags) { + if (tag.product != null) { + groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1; + } + } + return groupedTags; + } + + static List createInitialSelectedProducts( + List? tags, List? subspaces) { + final Map productCounts = {}; + + if (tags != null) { + for (var tag in tags) { + if (tag.product != null) { + productCounts[tag.product!] = (productCounts[tag.product!] ?? 0) + 1; + } + } + } + + if (subspaces != null) { + for (var subspace in subspaces) { + if (subspace.tags != null) { + for (var tag in subspace.tags!) { + if (tag.product != null) { + productCounts[tag.product!] = + (productCounts[tag.product!] ?? 0) + 1; + } + } + } + } + } + + return productCounts.entries + .map((entry) => SelectedProduct( + productId: entry.key.uuid, + count: entry.value, + productName: entry.key.name ?? 'Unnamed', + product: entry.key, + )) + .toList(); + } +} From eb53671e3a00a56a7d2df69058176dbbd3c4cc97 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 20 Jan 2025 21:47:22 +0400 Subject: [PATCH 109/175] edit flow --- .../assign_tag/bloc/assign_tag_bloc.dart | 17 +- .../assign_tag/views/assign_tag_dialog.dart | 6 +- .../bloc/assign_tag_model_bloc.dart | 3 +- .../views/assign_tag_models_dialog.dart | 147 ++++++++++++++---- .../spaces_management/helper/tag_helper.dart | 16 +- .../bloc/create_space_model_bloc.dart | 102 +++++++++--- .../models/subspace_template_model.dart | 24 ++- .../space_model/models/tag_model.dart | 13 +- .../dialog/create_space_model_dialog.dart | 42 ++--- .../widgets/tag_chips_display_widget.dart | 98 ++++-------- .../views/add_device_type_model_widget.dart | 19 ++- 11 files changed, 315 insertions(+), 172 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart index 6adcc6a7..4a85348f 100644 --- a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart @@ -3,8 +3,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart'; -class AssignTagBloc - extends Bloc { +class AssignTagBloc extends Bloc { AssignTagBloc() : super(AssignTagInitial()) { on((event, emit) { final initialTags = event.initialTags ?? []; @@ -40,7 +39,7 @@ class AssignTagBloc (index) => Tag( tag: '', product: selectedProduct.product, - location: 'None', + location: 'Main Space', ), )); } @@ -55,8 +54,7 @@ class AssignTagBloc on((event, emit) { final currentState = state; - if (currentState is AssignTagLoaded && - currentState.tags.isNotEmpty) { + if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { final tags = List.from(currentState.tags); tags[event.index].tag = event.tag; emit(AssignTagLoaded( @@ -70,8 +68,7 @@ class AssignTagBloc on((event, emit) { final currentState = state; - if (currentState is AssignTagLoaded && - currentState.tags.isNotEmpty) { + if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { final tags = List.from(currentState.tags); // Use copyWith for immutability @@ -88,8 +85,7 @@ class AssignTagBloc on((event, emit) { final currentState = state; - if (currentState is AssignTagLoaded && - currentState.tags.isNotEmpty) { + if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { final tags = List.from(currentState.tags); emit(AssignTagLoaded( @@ -103,8 +99,7 @@ class AssignTagBloc on((event, emit) { final currentState = state; - if (currentState is AssignTagLoaded && - currentState.tags.isNotEmpty) { + if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { final updatedTags = List.from(currentState.tags) ..remove(event.tagToDelete); diff --git a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart index 31f9bec1..959c83df 100644 --- a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart +++ b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart @@ -224,7 +224,7 @@ class AssignTagDialog extends StatelessWidget { DataCell( DropdownButtonHideUnderline( child: DropdownButton( - value: tag.location ?? 'None', + value: tag.location ?? 'Main', dropdownColor: ColorsManager .whiteColors, // Dropdown background style: const TextStyle( @@ -232,9 +232,9 @@ class AssignTagDialog extends StatelessWidget { .black), // Style for selected text items: [ const DropdownMenuItem( - value: 'None', + value: 'Main Space', child: Text( - 'None', + 'Main Space', style: TextStyle( color: ColorsManager .textPrimaryColor), diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart index ce4a38c2..c8c4cea4 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart @@ -40,7 +40,7 @@ class AssignTagModelBloc (index) => TagModel( tag: '', product: selectedProduct.product, - location: 'None', + location: 'Main Space', ), )); } @@ -123,6 +123,7 @@ class AssignTagModelBloc bool _validateTags(List tags) { if (tags.isEmpty) { + print("tags empty"); return false; } final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index e6242c5e..82a721f0 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -11,6 +11,7 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assig import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class AssignTagModelsDialog extends StatelessWidget { @@ -40,8 +41,11 @@ class AssignTagModelsDialog extends StatelessWidget { @override Widget build(BuildContext context) { - final List locations = - (subspaces ?? []).map((subspace) => subspace.subspaceName).toList(); + final List locations = (subspaces ?? []) + .map((subspace) => subspace.subspaceName) + .toList() + ..add('Main Space'); + return BlocProvider( create: (_) => AssignTagModelBloc() ..add(InitializeTagModels( @@ -173,7 +177,8 @@ class AssignTagModelsDialog extends StatelessWidget { width: double .infinity, // Ensure full width for dropdown child: DialogTextfieldDropdown( - items: availableTags ?? [], + items: availableTags, + initialValue: tag.tag, onSelected: (value) { controller.text = value; context @@ -223,26 +228,55 @@ class AssignTagModelsDialog extends StatelessWidget { children: [ const SizedBox(width: 10), Expanded( - child: CancelButton( - label: 'Add New Device', - onPressed: () async { - Navigator.of(context).pop(); - final assignedTags = {}; - for (var tag in state.tags) { - if (tag.location == null || subspaces == null) { - continue; - } - for (var subspace in subspaces!) { - if (tag.location == subspace.subspaceName) { - subspace.tags ??= []; - subspace.tags!.add(tag); - assignedTags.add(tag); - break; + child: Builder( + builder: (buttonContext) => CancelButton( + label: 'Add New Device', + onPressed: () async { + Navigator.of(context).pop(); + + for (var tag in state.tags) { + if (tag.location == null || subspaces == null) { + continue; } + + final previousTagSubspace = + checkTagExistInSubspace(tag, subspaces ?? []); + + if (tag.location == 'Main Space') { + removeTagFromSubspace(tag, previousTagSubspace); + } else if (tag.location != + previousTagSubspace?.subspaceName) { + removeTagFromSubspace(tag, previousTagSubspace); + moveToNewSubspace(tag, subspaces ?? []); + state.tags.removeWhere( + (t) => t.internalId == tag.internalId); + } else { + updateTagInSubspace(tag, previousTagSubspace); + state.tags.removeWhere( + (t) => t.internalId == tag.internalId); + } + + await showDialog( + barrierDismissible: false, + context: + Navigator.of(context, rootNavigator: true) + .context, + builder: (context) => AddDeviceTypeModelWidget( + products: products, + subspaces: subspaces, + isCreate: false, + initialSelectedProducts: addedProducts, + allTags: allTags, + spaceName: spaceName, + spaceTagModels: state.tags, + onUpdate: (tags, subspaces) { + onUpdate?.call(state.tags, subspaces); + }, + ), + ); } - } - state.tags.removeWhere(assignedTags.contains); - }, + }, + ), ), ), const SizedBox(width: 10), @@ -256,23 +290,38 @@ class AssignTagModelsDialog extends StatelessWidget { onPressed: state.isSaveEnabled ? () async { Navigator.of(context).pop(); - final assignedTags = {}; + for (var tag in state.tags) { if (tag.location == null || subspaces == null) { continue; } - for (var subspace in subspaces!) { - if (tag.location == subspace.subspaceName) { - subspace.tags ??= []; - subspace.tags!.add(tag); - assignedTags.add(tag); - break; - } + + final previousTagSubspace = + checkTagExistInSubspace( + tag, subspaces ?? []); + + if (tag.location == 'Main Space') { + removeTagFromSubspace( + tag, previousTagSubspace); + } else if (tag.location != + previousTagSubspace?.subspaceName) { + removeTagFromSubspace( + tag, previousTagSubspace); + moveToNewSubspace(tag, subspaces ?? []); + state.tags.removeWhere( + (t) => t.internalId == tag.internalId); + } else { + updateTagInSubspace( + tag, previousTagSubspace); + state.tags.removeWhere( + (t) => t.internalId == tag.internalId); } } - state.tags.removeWhere(assignedTags.contains); - onUpdate!(state.tags,subspaces); + print("tryinh yo save"); + + onUpdate?.call(state.tags, subspaces); + } : null, child: const Text('Save'), @@ -302,4 +351,40 @@ class AssignTagModelsDialog extends StatelessWidget { .contains(tagValue)) .toList(); } + + void removeTagFromSubspace(TagModel tag, SubspaceTemplateModel? subspace) { + subspace?.tags?.removeWhere((t) => t.internalId == tag.internalId); + } + + SubspaceTemplateModel? checkTagExistInSubspace( + TagModel tag, List? subspaces) { + if (subspaces == null) return null; + for (var subspace in subspaces) { + if (subspace.tags == null) return null; + for (var t in subspace.tags!) { + if (tag.internalId == t.internalId) return subspace; + } + } + return null; + } + + void moveToNewSubspace(TagModel tag, List subspaces) { + final targetSubspace = subspaces + .firstWhere((subspace) => subspace.subspaceName == tag.location); + + targetSubspace.tags ??= []; + if (targetSubspace.tags?.any((t) => t.internalId == tag.internalId) != + true) { + targetSubspace.tags?.add(tag); + } + } + + void updateTagInSubspace(TagModel tag, SubspaceTemplateModel? subspace) { + final currentTag = subspace?.tags?.firstWhere( + (t) => t.internalId == tag.internalId, + ); + if (currentTag != null) { + currentTag.tag = tag.tag; + } + } } diff --git a/lib/pages/spaces_management/helper/tag_helper.dart b/lib/pages/spaces_management/helper/tag_helper.dart index ed4525ae..bfff02a9 100644 --- a/lib/pages/spaces_management/helper/tag_helper.dart +++ b/lib/pages/spaces_management/helper/tag_helper.dart @@ -17,15 +17,19 @@ class TagHelper { if (subspaces != null) { for (var subspace in subspaces) { if (subspace.tags != null) { - initialTags.addAll( - subspace.tags!.map( - (tag) => tag.copyWith(location: subspace.subspaceName), - ), - ); + for (var existingTag in subspace.tags!) { + initialTags.addAll( + subspace.tags!.map( + (tag) => tag.copyWith( + location: subspace.subspaceName, + internalId: existingTag.internalId, + tag: existingTag.tag), + ), + ); + } } } } - return initialTags; } diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index ec17482f..6f2f2018 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -3,6 +3,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; @@ -67,36 +68,102 @@ class CreateSpaceModelBloc _space = event.spaceTemplate; emit(CreateSpaceModelLoaded(_space!)); }); - on((event, emit) { final currentState = state; if (currentState is CreateSpaceModelLoaded) { - final updatedSpace = currentState.space.copyWith( - subspaceModels: [ - ...(_space!.subspaceModels ?? []), - ...event.subspaces, - ], - ); + final eventSubspaceIds = + event.subspaces.map((e) => e.internalId).toSet(); + + // Update or retain subspaces + final updatedSubspaces = currentState.space.subspaceModels + ?.where((subspace) => + eventSubspaceIds.contains(subspace.internalId)) + .map((subspace) { + final matchingEventSubspace = event.subspaces.firstWhere( + (e) => e.internalId == subspace.internalId, + orElse: () => subspace, + ); + + // Update the subspace's tags + final eventTagIds = matchingEventSubspace.tags + ?.map((e) => e.internalId) + .toSet() ?? + {}; + + final updatedTags = [ + ...?subspace.tags?.map((tag) { + final matchingTag = + matchingEventSubspace.tags?.firstWhere( + (e) => e.internalId == tag.internalId, + orElse: () => tag, + ); + final isUpdated = matchingTag != tag; + return isUpdated + ? tag.copyWith(tag: matchingTag?.tag) + : tag; + }) ?? + [], + ...?matchingEventSubspace.tags?.where( + (e) => + subspace.tags + ?.every((t) => t.internalId != e.internalId) ?? + true, + ) ?? + [], + ]; + return subspace.copyWith( + subspaceName: matchingEventSubspace.subspaceName, + tags: updatedTags, + ); + }).toList() ?? + []; + + // Add new subspaces + event.subspaces + .where((e) => + updatedSubspaces.every((s) => s.internalId != e.internalId)) + .forEach((newSubspace) { + updatedSubspaces.add(newSubspace); + }); + + final updatedSpace = + currentState.space.copyWith(subspaceModels: updatedSubspaces); + emit(CreateSpaceModelLoaded(updatedSpace)); } else { emit(CreateSpaceModelError("Space template not initialized")); } }); - - on((event, emit) { final currentState = state; if (currentState is CreateSpaceModelLoaded) { - final updatedTags = currentState.space.copyWith( - tags: [ - ...(_space!.tags ?? []), - ...event.tags, - ], - ); - emit(CreateSpaceModelLoaded(updatedTags)); + final eventTagIds = event.tags.map((e) => e.internalId).toSet(); + + final updatedTags = currentState.space.tags + ?.where((tag) => eventTagIds.contains(tag.internalId)) + .map((tag) { + final matchingEventTag = event.tags.firstWhere( + (e) => e.internalId == tag.internalId, + orElse: () => tag, + ); + return matchingEventTag != tag + ? tag.copyWith(tag: matchingEventTag.tag) + : tag; + }).toList() ?? + []; + + event.tags + .where( + (e) => updatedTags.every((t) => t.internalId != e.internalId)) + .forEach((e) { + updatedTags.add(e); + }); + + emit(CreateSpaceModelLoaded( + currentState.space.copyWith(tags: updatedTags))); } else { emit(CreateSpaceModelError("Space template not initialized")); } @@ -106,7 +173,6 @@ class CreateSpaceModelBloc final currentState = state; if (currentState is CreateSpaceModelLoaded) { if (event.name.trim().isEmpty) { - emit(CreateSpaceModelLoaded( currentState.space, errorMessage: "Model name cannot be empty", @@ -114,7 +180,7 @@ class CreateSpaceModelBloc } else { final updatedSpaceModel = currentState.space.copyWith(modelName: event.name); - + emit(CreateSpaceModelLoaded(updatedSpaceModel)); } } else { diff --git a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart index ac71c6b1..6c73741b 100644 --- a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart @@ -1,22 +1,28 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:uuid/uuid.dart'; class SubspaceTemplateModel { final String? uuid; String subspaceName; final bool disabled; List? tags; + String internalId; SubspaceTemplateModel({ this.uuid, required this.subspaceName, required this.disabled, this.tags, - }); + String? internalId, + }) : internalId = internalId ?? const Uuid().v4(); factory SubspaceTemplateModel.fromJson(Map json) { + final String internalId = json['internalId'] ?? const Uuid().v4(); + return SubspaceTemplateModel( uuid: json['uuid'] ?? '', subspaceName: json['subspaceName'] ?? '', + internalId: internalId, disabled: json['disabled'] ?? false, tags: (json['tags'] as List?) ?.map((item) => TagModel.fromJson(item)) @@ -33,4 +39,20 @@ class SubspaceTemplateModel { 'tags': tags?.map((e) => e.toJson()).toList() ?? [], }; } + + SubspaceTemplateModel copyWith({ + String? uuid, + String? subspaceName, + bool? disabled, + List? tags, + String? internalId, + }) { + return SubspaceTemplateModel( + uuid: uuid ?? this.uuid, + subspaceName: subspaceName ?? this.subspaceName, + disabled: disabled ?? this.disabled, + tags: tags ?? this.tags, + internalId: internalId ?? this.internalId, + ); + } } diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart index 99008e76..48f89167 100644 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -30,15 +30,16 @@ class TagModel { ); } - TagModel copyWith({ - String? tag, - ProductModel? product, - String? location, - }) { + TagModel copyWith( + {String? tag, + ProductModel? product, + String? location, + String? internalId}) { return TagModel( tag: tag ?? this.tag, product: product ?? this.product, location: location ?? this.location, + internalId: internalId ?? this.internalId, ); } @@ -58,4 +59,4 @@ extension TagModelExtensions on TagModel { ..tag = tag ?? '' ..productUuid = product?.uuid; } -} \ No newline at end of file +} diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index e5dea2b9..e671410c 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -44,15 +44,15 @@ class CreateSpaceModelDialog extends StatelessWidget { child: BlocProvider( create: (_) { final bloc = CreateSpaceModelBloc(_spaceModelApi); - if (spaceModel != null) { - bloc.add(UpdateSpaceTemplate(spaceModel!)); - } else { - bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( - modelName: '', - subspaceModels: const [], - ))); - } - + if (spaceModel != null) { + bloc.add(UpdateSpaceTemplate(spaceModel!)); + } else { + bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( + modelName: '', + subspaceModels: const [], + ))); + } + spaceNameController.addListener(() { bloc.add(UpdateSpaceTemplateName(name: spaceNameController.text)); }); @@ -72,7 +72,9 @@ class CreateSpaceModelDialog extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - spaceModel?.uuid == null ? 'Create New Space Model': 'Edit Space Model', + spaceModel?.uuid == null + ? 'Create New Space Model' + : 'Edit Space Model', style: Theme.of(context) .textTheme .headlineLarge @@ -127,16 +129,18 @@ class CreateSpaceModelDialog extends StatelessWidget { allTags: allTags, spaceNameController: spaceNameController, onLoad: (tags, subspaces) { - if(subspaces!=null){ - context - .read() - .add(AddSubspacesToSpaceTemplate(subspaces)); - } - if(tags!=null){ + if (context.read().state + is CreateSpaceModelLoaded) { + if (subspaces != null) { context - .read() - .add(AddTagsToSpaceTemplate(tags)); - + .read() + .add(AddSubspacesToSpaceTemplate(subspaces)); + } + if (tags != null) { + context + .read() + .add(AddTagsToSpaceTemplate(tags)); + } } }, ), diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index d83db22a..1f7e0d40 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; @@ -53,7 +54,7 @@ class TagChipDisplay extends StatelessWidget { runSpacing: 8.0, children: [ // Combine tags from spaceModel and subspaces - ..._groupTags([ + ...TagHelper.groupTags([ ...?spaceModel?.tags, ...?spaceModel?.subspaceModels ?.expand((subspace) => subspace.tags ?? []) @@ -84,24 +85,32 @@ class TagChipDisplay extends StatelessWidget { ), GestureDetector( onTap: () async { - Navigator.of(context).pop(); + // Use the Navigator's context for showDialog + final navigatorContext = + Navigator.of(context).overlay?.context; - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => AddDeviceTypeModelWidget( - isCreate: false, - products: products, - subspaces: subspaces, - allTags: allTags, - spaceName: spaceNameController.text, - spaceTagModels: spaceModel?.tags, - initialSelectedProducts: - _createInitialSelectedProducts( - spaceModel?.tags, spaceModel?.subspaceModels), - ), - ); - // Edit action + if (navigatorContext != null) { + await showDialog( + barrierDismissible: false, + context: navigatorContext, + builder: (context) => AssignTagModelsDialog( + products: products, + subspaces: subspaces, + allTags: allTags, + initialTags: TagHelper.generateInitialTags( + subspaces: subspaces, + spaceTagModels: spaceModel?.tags ?? []), + title: 'Edit Device', + addedProducts: + TagHelper.createInitialSelectedProducts( + spaceModel?.tags ?? [], subspaces), + spaceName: spaceModel?.modelName ?? '', + onUpdate: (tags, subspaces){ + print("here"); + onLoad?.call(tags, subspaces);} + ), + ); + } }, child: Chip( label: const Text( @@ -130,11 +139,7 @@ class TagChipDisplay extends StatelessWidget { allTags: allTags, spaceName: spaceNameController.text, isCreate: true, - onLoad: (tags, subspaces) { - if (onLoad != null) { - onLoad!(tags, subspaces); - } - }, + onLoad: (tags, subspaces) => onLoad?.call(tags, subspaces), ), ); }, @@ -147,49 +152,4 @@ class TagChipDisplay extends StatelessWidget { ), ); } - - Map _groupTags(List tags) { - final Map groupedTags = {}; - for (var tag in tags) { - if (tag.product != null) { - groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1; - } - } - return groupedTags; - } - - List _createInitialSelectedProducts( - List? tags, List? subspaces) { - final Map productCounts = {}; - - if (tags != null) { - for (var tag in tags) { - if (tag.product != null) { - productCounts[tag.product!] = (productCounts[tag.product!] ?? 0) + 1; - } - } - } - - if (subspaces != null) { - for (var subspace in subspaces) { - if (subspace.tags != null) { - for (var tag in subspace.tags!) { - if (tag.product != null) { - productCounts[tag.product!] = - (productCounts[tag.product!] ?? 0) + 1; - } - } - } - } - } - - return productCounts.entries - .map((entry) => SelectedProduct( - productId: entry.key.uuid, - count: entry.value, - productName: entry.key.name ?? 'Unnamed', - product: entry.key, - )) - .toList(); - } } diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index 8ed903b1..e324fa06 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -23,6 +23,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final bool isCreate; final void Function( List? tags, List? subspaces)? onLoad; + final void Function( + List? tags, List? subspaces)? onUpdate; const AddDeviceTypeModelWidget({ super.key, @@ -34,10 +36,18 @@ class AddDeviceTypeModelWidget extends StatelessWidget { required this.spaceName, required this.isCreate, this.onLoad, + this.onUpdate, }); @override Widget build(BuildContext context) { + + if (spaceTagModels != null) { + for (var tag in spaceTagModels!) { + print(tag.tag); + } + } + final size = MediaQuery.of(context).size; final crossAxisCount = size.width > 1200 ? 8 @@ -123,15 +133,12 @@ class AddDeviceTypeModelWidget extends StatelessWidget { spaceTagModels: spaceTagModels, subspaces: subspaces, ); - if (isCreate) { - Navigator.of(context).pop(); - } final dialogTitle = initialTags.isNotEmpty ? 'Edit Device' : 'Assign Tags'; + Navigator.of(context).pop(); await showDialog( - barrierDismissible: false, context: context, builder: (context) => AssignTagModelsDialog( products: products, @@ -142,9 +149,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { initialTags: state.initialTag, title: dialogTitle, onUpdate: (tags, subspaces) { - if (onLoad != null) { - onLoad!(tags, subspaces); - } + onLoad?.call(tags, subspaces); }, ), ); From 0e912207e533746a7cf57863b687e727e99a0cde Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 21 Jan 2025 11:41:53 +0400 Subject: [PATCH 110/175] flow --- .../bloc/assign_tag_model_bloc.dart | 1 - .../views/assign_tag_models_dialog.dart | 17 ++++----- .../widgets/tag_chips_display_widget.dart | 36 ++++++++++--------- .../views/add_device_type_model_widget.dart | 9 ++--- 4 files changed, 29 insertions(+), 34 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart index c8c4cea4..b5251868 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart @@ -123,7 +123,6 @@ class AssignTagModelBloc bool _validateTags(List tags) { if (tags.isEmpty) { - print("tags empty"); return false; } final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 82a721f0..a6b6b7f0 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -232,8 +232,6 @@ class AssignTagModelsDialog extends StatelessWidget { builder: (buttonContext) => CancelButton( label: 'Add New Device', onPressed: () async { - Navigator.of(context).pop(); - for (var tag in state.tags) { if (tag.location == null || subspaces == null) { continue; @@ -255,13 +253,13 @@ class AssignTagModelsDialog extends StatelessWidget { state.tags.removeWhere( (t) => t.internalId == tag.internalId); } - + } + if (context.mounted) { await showDialog( barrierDismissible: false, - context: - Navigator.of(context, rootNavigator: true) - .context, - builder: (context) => AddDeviceTypeModelWidget( + context: context, + builder: (dialogContext) => + AddDeviceTypeModelWidget( products: products, subspaces: subspaces, isCreate: false, @@ -271,6 +269,7 @@ class AssignTagModelsDialog extends StatelessWidget { spaceTagModels: state.tags, onUpdate: (tags, subspaces) { onUpdate?.call(state.tags, subspaces); + Navigator.of(context).pop(); }, ), ); @@ -318,10 +317,8 @@ class AssignTagModelsDialog extends StatelessWidget { (t) => t.internalId == tag.internalId); } } - print("tryinh yo save"); - + onUpdate?.call(state.tags, subspaces); - } : null, child: const Text('Save'), diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 1f7e0d40..18efd501 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -94,21 +94,20 @@ class TagChipDisplay extends StatelessWidget { barrierDismissible: false, context: navigatorContext, builder: (context) => AssignTagModelsDialog( - products: products, - subspaces: subspaces, - allTags: allTags, - initialTags: TagHelper.generateInitialTags( - subspaces: subspaces, - spaceTagModels: spaceModel?.tags ?? []), - title: 'Edit Device', - addedProducts: - TagHelper.createInitialSelectedProducts( - spaceModel?.tags ?? [], subspaces), - spaceName: spaceModel?.modelName ?? '', - onUpdate: (tags, subspaces){ - print("here"); - onLoad?.call(tags, subspaces);} - ), + products: products, + subspaces: subspaces, + allTags: allTags, + initialTags: TagHelper.generateInitialTags( + subspaces: subspaces, + spaceTagModels: spaceModel?.tags ?? []), + title: 'Edit Device', + addedProducts: + TagHelper.createInitialSelectedProducts( + spaceModel?.tags ?? [], subspaces), + spaceName: spaceModel?.modelName ?? '', + onUpdate: (tags, subspaces) { + onLoad?.call(tags, subspaces); + }), ); } }, @@ -139,7 +138,12 @@ class TagChipDisplay extends StatelessWidget { allTags: allTags, spaceName: spaceNameController.text, isCreate: true, - onLoad: (tags, subspaces) => onLoad?.call(tags, subspaces), + onUpdate: (tags, subspaces) { + onLoad?.call(tags, subspaces); + }, + onLoad: (tags, subspaces) { + onLoad?.call(tags, subspaces); + }, ), ); }, diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index e324fa06..7944eb4f 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -41,12 +41,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { @override Widget build(BuildContext context) { - - if (spaceTagModels != null) { - for (var tag in spaceTagModels!) { - print(tag.tag); - } - } + final size = MediaQuery.of(context).size; final crossAxisCount = size.width > 1200 @@ -149,7 +144,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { initialTags: state.initialTag, title: dialogTitle, onUpdate: (tags, subspaces) { - onLoad?.call(tags, subspaces); + onUpdate?.call(tags, subspaces); }, ), ); From 81e9e58627cd4fa5166417feacf64fb12f9287d8 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 21 Jan 2025 15:21:25 +0400 Subject: [PATCH 111/175] fixed duplicate tag issue --- .../bloc/assign_tag_model_bloc.dart | 17 +- .../bloc/assign_tag_model_state.dart | 8 +- .../views/assign_tag_models_dialog.dart | 512 +++++++++--------- .../bloc/create_space_model_bloc.dart | 3 +- .../dialog/create_space_model_dialog.dart | 4 + 5 files changed, 283 insertions(+), 261 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart index b5251868..92d7c0e1 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart @@ -47,14 +47,13 @@ class AssignTagModelBloc } emit(AssignTagModelLoaded( - tags: allTags, - isSaveEnabled: _validateTags(allTags), - )); + tags: allTags, + isSaveEnabled: _validateTags(allTags), + errorMessage: '')); }); on((event, emit) { final currentState = state; - if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) { final tags = List.from(currentState.tags); @@ -122,9 +121,7 @@ class AssignTagModelBloc } bool _validateTags(List tags) { - if (tags.isEmpty) { - return false; - } + final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); final isValid = uniqueTags.length == tags.length && !hasEmptyTag; @@ -133,7 +130,11 @@ class AssignTagModelBloc String? _getValidationError(List tags) { final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); - if (hasEmptyTag) return 'Tags cannot be empty.'; + if (hasEmptyTag) { + return 'Tags cannot be empty.'; + } + + // Check for duplicate tags final duplicateTags = tags .map((tag) => tag.tag?.trim() ?? '') .fold>({}, (map, tag) { diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart index 9812a293..a51a9e8f 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart @@ -5,7 +5,7 @@ abstract class AssignTagModelState extends Equatable { const AssignTagModelState(); @override - List get props => []; + List get props => []; } class AssignTagModelInitial extends AssignTagModelState {} @@ -15,7 +15,7 @@ class AssignTagModelLoading extends AssignTagModelState {} class AssignTagModelLoaded extends AssignTagModelState { final List tags; final bool isSaveEnabled; - final String? errorMessage; + final String? errorMessage; const AssignTagModelLoaded({ required this.tags, @@ -24,7 +24,7 @@ class AssignTagModelLoaded extends AssignTagModelState { }); @override - List get props => [tags, isSaveEnabled]; + List get props => [tags, isSaveEnabled, errorMessage]; } class AssignTagModelError extends AssignTagModelState { @@ -33,5 +33,5 @@ class AssignTagModelError extends AssignTagModelState { const AssignTagModelError(this.errorMessage); @override - List get props => [errorMessage]; + List get props => [errorMessage]; } diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index a6b6b7f0..3a2499f9 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -47,249 +47,202 @@ class AssignTagModelsDialog extends StatelessWidget { ..add('Main Space'); return BlocProvider( - create: (_) => AssignTagModelBloc() - ..add(InitializeTagModels( - initialTags: initialTags, - addedProducts: addedProducts, - )), - child: BlocBuilder( - builder: (context, state) { - if (state is AssignTagModelLoaded) { - final controllers = List.generate( - state.tags.length, - (index) => TextEditingController(text: state.tags[index].tag), - ); + create: (_) => AssignTagModelBloc() + ..add(InitializeTagModels( + initialTags: initialTags, + addedProducts: addedProducts, + )), + child: BlocListener( + listener: (context, state) {}, + child: BlocBuilder( + builder: (context, state) { + if (state is AssignTagModelLoaded) { + final controllers = List.generate( + state.tags.length, + (index) => TextEditingController(text: state.tags[index].tag), + ); - return AlertDialog( - title: Text(title), - backgroundColor: ColorsManager.whiteColors, - content: SingleChildScrollView( - child: Column( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(20), - child: DataTable( - headingRowColor: WidgetStateProperty.all( - ColorsManager.dataHeaderGrey), - border: TableBorder.all( - color: ColorsManager.dataHeaderGrey, - width: 1, + return AlertDialog( + title: Text(title), + backgroundColor: ColorsManager.whiteColors, + content: SingleChildScrollView( + child: Column( + children: [ + ClipRRect( borderRadius: BorderRadius.circular(20), - ), - columns: [ - DataColumn( - label: Text('#', - style: - Theme.of(context).textTheme.bodyMedium)), - DataColumn( - label: Text('Device', - style: - Theme.of(context).textTheme.bodyMedium)), - DataColumn( - numeric: false, - headingRowAlignment: MainAxisAlignment.start, - label: Text('Tag', - style: - Theme.of(context).textTheme.bodyMedium)), - DataColumn( - label: Text('Location', - style: - Theme.of(context).textTheme.bodyMedium)), - ], - rows: state.tags.isEmpty - ? [ - const DataRow(cells: [ - DataCell( - Center( - child: Text( - 'No Data Available', - style: TextStyle( - fontSize: 14, - color: ColorsManager.lightGrayColor, + child: DataTable( + headingRowColor: WidgetStateProperty.all( + ColorsManager.dataHeaderGrey), + border: TableBorder.all( + color: ColorsManager.dataHeaderGrey, + width: 1, + borderRadius: BorderRadius.circular(20), + ), + columns: [ + DataColumn( + label: Text('#', + style: Theme.of(context) + .textTheme + .bodyMedium)), + DataColumn( + label: Text('Device', + style: Theme.of(context) + .textTheme + .bodyMedium)), + DataColumn( + numeric: false, + headingRowAlignment: MainAxisAlignment.start, + label: Text('Tag', + style: Theme.of(context) + .textTheme + .bodyMedium)), + DataColumn( + label: Text('Location', + style: Theme.of(context) + .textTheme + .bodyMedium)), + ], + rows: state.tags.isEmpty + ? [ + const DataRow(cells: [ + DataCell( + Center( + child: Text( + 'No Data Available', + style: TextStyle( + fontSize: 14, + color: + ColorsManager.lightGrayColor, + ), + ), ), ), - ), - ), - DataCell(SizedBox()), - DataCell(SizedBox()), - DataCell(SizedBox()), - ]) - ] - : List.generate(state.tags.length, (index) { - final tag = state.tags[index]; - final controller = controllers[index]; - final availableTags = getAvailableTags( - allTags ?? [], state.tags, tag); + DataCell(SizedBox()), + DataCell(SizedBox()), + DataCell(SizedBox()), + ]) + ] + : List.generate(state.tags.length, (index) { + final tag = state.tags[index]; + final controller = controllers[index]; + final availableTags = getAvailableTags( + allTags ?? [], state.tags, tag); - return DataRow( - cells: [ - DataCell(Text(index.toString())), - DataCell( - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - tag.product?.name ?? 'Unknown', - overflow: TextOverflow.ellipsis, - )), - const SizedBox(width: 10), + return DataRow( + cells: [ + DataCell(Text(index.toString())), + DataCell( + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + tag.product?.name ?? 'Unknown', + overflow: TextOverflow.ellipsis, + )), + const SizedBox(width: 10), + Container( + width: 20.0, + height: 20.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: ColorsManager + .lightGrayColor, + width: 1.0, + ), + ), + child: IconButton( + icon: const Icon( + Icons.close, + color: ColorsManager + .lightGreyColor, + size: 16, + ), + onPressed: () { + context + .read< + AssignTagModelBloc>() + .add(DeleteTagModel( + tagToDelete: tag, + tags: state.tags)); + }, + tooltip: 'Delete Tag', + padding: EdgeInsets.zero, + constraints: + const BoxConstraints(), + ), + ), + ], + ), + ), + DataCell( Container( - width: 20.0, - height: 20.0, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: ColorsManager - .lightGrayColor, - width: 1.0, + alignment: Alignment + .centerLeft, // Align cell content to the left + child: SizedBox( + width: double + .infinity, // Ensure full width for dropdown + child: DialogTextfieldDropdown( + items: availableTags, + initialValue: tag.tag, + onSelected: (value) { + controller.text = value; + context + .read< + AssignTagModelBloc>() + .add(UpdateTag( + index: index, + tag: value, + )); + }, ), ), - child: IconButton( - icon: const Icon( - Icons.close, - color: ColorsManager - .lightGreyColor, - size: 16, - ), - onPressed: () { - context - .read() - .add(DeleteTagModel( - tagToDelete: tag, - tags: state.tags)); - }, - tooltip: 'Delete Tag', - padding: EdgeInsets.zero, - constraints: - const BoxConstraints(), - ), - ), - ], - ), - ), - DataCell( - Container( - alignment: Alignment - .centerLeft, // Align cell content to the left - child: SizedBox( - width: double - .infinity, // Ensure full width for dropdown - child: DialogTextfieldDropdown( - items: availableTags, - initialValue: tag.tag, - onSelected: (value) { - controller.text = value; - context - .read() - .add(UpdateTag( - index: index, - tag: value, - )); - }, ), ), - ), - ), - DataCell( - SizedBox( - width: double.infinity, - child: DialogDropdown( - items: locations, - selectedValue: - tag.location ?? 'None', - onSelected: (value) { - context - .read() - .add(UpdateLocation( - index: index, - location: value, - )); - }, - )), - ), - ], - ); - }), - ), - ), - if (state.errorMessage != null) - Text( - state.errorMessage!, - style: const TextStyle(color: ColorsManager.warningRed), - ), - ], - ), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - const SizedBox(width: 10), - Expanded( - child: Builder( - builder: (buttonContext) => CancelButton( - label: 'Add New Device', - onPressed: () async { - for (var tag in state.tags) { - if (tag.location == null || subspaces == null) { - continue; - } - - final previousTagSubspace = - checkTagExistInSubspace(tag, subspaces ?? []); - - if (tag.location == 'Main Space') { - removeTagFromSubspace(tag, previousTagSubspace); - } else if (tag.location != - previousTagSubspace?.subspaceName) { - removeTagFromSubspace(tag, previousTagSubspace); - moveToNewSubspace(tag, subspaces ?? []); - state.tags.removeWhere( - (t) => t.internalId == tag.internalId); - } else { - updateTagInSubspace(tag, previousTagSubspace); - state.tags.removeWhere( - (t) => t.internalId == tag.internalId); - } - } - if (context.mounted) { - await showDialog( - barrierDismissible: false, - context: context, - builder: (dialogContext) => - AddDeviceTypeModelWidget( - products: products, - subspaces: subspaces, - isCreate: false, - initialSelectedProducts: addedProducts, - allTags: allTags, - spaceName: spaceName, - spaceTagModels: state.tags, - onUpdate: (tags, subspaces) { - onUpdate?.call(state.tags, subspaces); - Navigator.of(context).pop(); - }, - ), - ); - } - }, + DataCell( + SizedBox( + width: double.infinity, + child: DialogDropdown( + items: locations, + selectedValue: + tag.location ?? 'None', + onSelected: (value) { + context + .read< + AssignTagModelBloc>() + .add(UpdateLocation( + index: index, + location: value, + )); + }, + )), + ), + ], + ); + }), + ), ), - ), + if (state.errorMessage != null) + Text( + state.errorMessage!, + style: const TextStyle( + color: ColorsManager.warningRed), + ), + ], ), - const SizedBox(width: 10), - Expanded( - child: DefaultButton( - borderRadius: 10, - backgroundColor: state.isSaveEnabled - ? ColorsManager.secondaryColor - : ColorsManager.grayColor, - foregroundColor: ColorsManager.whiteColors, - onPressed: state.isSaveEnabled - ? () async { - Navigator.of(context).pop(); - + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + const SizedBox(width: 10), + Expanded( + child: Builder( + builder: (buttonContext) => CancelButton( + label: 'Add New Device', + onPressed: () async { for (var tag in state.tags) { if (tag.location == null || subspaces == null) { @@ -317,26 +270,89 @@ class AssignTagModelsDialog extends StatelessWidget { (t) => t.internalId == tag.internalId); } } + if (context.mounted) { + await showDialog( + barrierDismissible: false, + context: context, + builder: (dialogContext) => + AddDeviceTypeModelWidget( + products: products, + subspaces: subspaces, + isCreate: false, + initialSelectedProducts: addedProducts, + allTags: allTags, + spaceName: spaceName, + spaceTagModels: state.tags, + onUpdate: (tags, subspaces) { + onUpdate?.call(state.tags, subspaces); + Navigator.of(context).pop(); + }, + ), + ); + } + }, + ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + borderRadius: 10, + backgroundColor: state.isSaveEnabled + ? ColorsManager.secondaryColor + : ColorsManager.grayColor, + foregroundColor: ColorsManager.whiteColors, + onPressed: state.isSaveEnabled + ? () async { + Navigator.of(context).pop(); - onUpdate?.call(state.tags, subspaces); - } - : null, - child: const Text('Save'), - ), + for (var tag in state.tags) { + if (tag.location == null || + subspaces == null) { + continue; + } + + final previousTagSubspace = + checkTagExistInSubspace( + tag, subspaces ?? []); + + if (tag.location == 'Main Space') { + removeTagFromSubspace( + tag, previousTagSubspace); + } else if (tag.location != + previousTagSubspace?.subspaceName) { + removeTagFromSubspace( + tag, previousTagSubspace); + moveToNewSubspace(tag, subspaces ?? []); + state.tags.removeWhere((t) => + t.internalId == tag.internalId); + } else { + updateTagInSubspace( + tag, previousTagSubspace); + state.tags.removeWhere((t) => + t.internalId == tag.internalId); + } + } + + onUpdate?.call(state.tags, subspaces); + } + : null, + child: const Text('Save'), + ), + ), + const SizedBox(width: 10), + ], ), - const SizedBox(width: 10), ], - ), - ], - ); - } else if (state is AssignTagModelLoading) { - return const Center(child: CircularProgressIndicator()); - } else { - return const Center(child: Text('Something went wrong.')); - } - }, - ), - ); + ); + } else if (state is AssignTagModelLoading) { + return const Center(child: CircularProgressIndicator()); + } else { + return const Center(child: Text('Something went wrong.')); + } + }, + ), + )); } List getAvailableTags( diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 6f2f2018..33defb52 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -3,7 +3,6 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; @@ -53,6 +52,8 @@ class CreateSpaceModelBloc } }); + + on((event, emit) { emit(CreateSpaceModelLoading()); Future.delayed(const Duration(seconds: 1), () { diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index e671410c..a3f34e31 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -166,6 +166,10 @@ class CreateSpaceModelDialog extends StatelessWidget { modelName: spaceNameController.text.trim(), ); + if(updatedSpaceTemplate.uuid != null){ + + } + context.read().add( CreateSpaceTemplate( spaceTemplate: From 5060d2a66d8871ec87f4349729f078fee4269c41 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Tue, 21 Jan 2025 15:28:59 +0300 Subject: [PATCH 112/175] Updated side tree branch --- lib/common/widgets/custom_expansion_tile.dart | 8 +- lib/pages/space_tree/view/community_tile.dart | 35 +++++ .../space_tree/view/custom_expansion.dart | 127 ++++++++++++++++++ lib/pages/space_tree/view/space_tile.dart | 52 +++++++ .../space_tree/view/space_tree_view.dart | 8 +- 5 files changed, 221 insertions(+), 9 deletions(-) create mode 100644 lib/pages/space_tree/view/community_tile.dart create mode 100644 lib/pages/space_tree/view/custom_expansion.dart create mode 100644 lib/pages/space_tree/view/space_tile.dart diff --git a/lib/common/widgets/custom_expansion_tile.dart b/lib/common/widgets/custom_expansion_tile.dart index b6b33479..8df9b663 100644 --- a/lib/common/widgets/custom_expansion_tile.dart +++ b/lib/common/widgets/custom_expansion_tile.dart @@ -58,18 +58,16 @@ class CustomExpansionTileState extends State { children: [ // Checkbox with independent state management Checkbox( - value: widget.isSelected, + value: false, onChanged: (bool? value) { - if (widget.onItemSelected != null) { - widget.onItemSelected!(); - } + setState(() {}); }, side: WidgetStateBorderSide.resolveWith((states) { return const BorderSide(color: ColorsManager.grayBorder); }), fillColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { - return ColorsManager.blue1; + return ColorsManager.grayBorder; } else { return ColorsManager.checkBoxFillColor; } diff --git a/lib/pages/space_tree/view/community_tile.dart b/lib/pages/space_tree/view/community_tile.dart new file mode 100644 index 00000000..938aa682 --- /dev/null +++ b/lib/pages/space_tree/view/community_tile.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/space_tree/view/custom_expansion.dart'; + +class CommunityTileSpaceTree extends StatelessWidget { + final String title; + final List? children; + final bool isExpanded; + final bool isSelected; + final Function(String, bool) onExpansionChanged; + final Function() onItemSelected; + + const CommunityTileSpaceTree({ + super.key, + required this.title, + required this.isExpanded, + required this.onExpansionChanged, + required this.onItemSelected, + required this.isSelected, + this.children, + }); + + @override + Widget build(BuildContext context) { + return CustomExpansionTileSpaceTree( + title: title, + initiallyExpanded: isExpanded, + isSelected: isSelected, + onExpansionChanged: (bool expanded) { + onExpansionChanged(title, expanded); + }, + onItemSelected: onItemSelected, + children: children ?? [], + ); + } +} diff --git a/lib/pages/space_tree/view/custom_expansion.dart b/lib/pages/space_tree/view/custom_expansion.dart new file mode 100644 index 00000000..7314417b --- /dev/null +++ b/lib/pages/space_tree/view/custom_expansion.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class CustomExpansionTileSpaceTree extends StatefulWidget { + final String title; + final List? children; + final bool initiallyExpanded; + final bool isSelected; // Add this to track selection + final bool? isExpanded; // External control over expansion + final ValueChanged? onExpansionChanged; // Notify when expansion changes + final VoidCallback? onItemSelected; // Callback for selecting the item + + CustomExpansionTileSpaceTree({ + required this.title, + this.children, + this.initiallyExpanded = false, + this.isExpanded, // Allow external control over expansion + this.onExpansionChanged, // Notify when expansion changes + this.onItemSelected, // Trigger item selection when name is tapped + required this.isSelected, // Add this to initialize selection state + }); + + @override + CustomExpansionTileState createState() => CustomExpansionTileState(); +} + +class CustomExpansionTileState extends State { + bool _isExpanded = false; // Local expansion state + + @override + void initState() { + super.initState(); + _isExpanded = widget.initiallyExpanded; + } + + @override + void didUpdateWidget(CustomExpansionTileSpaceTree oldWidget) { + super.didUpdateWidget(oldWidget); + // Sync local state with external control of expansion state + if (widget.isExpanded != null && widget.isExpanded != _isExpanded) { + setState(() { + _isExpanded = widget.isExpanded!; + }); + } + } + + // Utility function to capitalize the first letter of the title + String _capitalizeFirstLetter(String text) { + if (text.isEmpty) return text; + return text[0].toUpperCase() + text.substring(1); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + children: [ + // Checkbox with independent state management + Checkbox( + value: widget.isSelected, + onChanged: (bool? value) { + if (widget.onItemSelected != null) { + widget.onItemSelected!(); + } + }, + side: WidgetStateBorderSide.resolveWith((states) { + return const BorderSide(color: ColorsManager.grayBorder); + }), + fillColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return ColorsManager.blue1; + } else { + return ColorsManager.checkBoxFillColor; + } + }), + checkColor: ColorsManager.whiteColors, + ), + // Expand/collapse icon, now wrapped in a GestureDetector for specific onTap + if (widget.children != null && widget.children!.isNotEmpty) + GestureDetector( + onTap: () { + setState(() { + _isExpanded = !_isExpanded; + widget.onExpansionChanged?.call(_isExpanded); + }); + }, + child: Icon( + _isExpanded ? Icons.keyboard_arrow_down : Icons.keyboard_arrow_right, + color: ColorsManager.lightGrayColor, + size: 16.0, // Adjusted size for better alignment + ), + ), + // The title text, wrapped in GestureDetector to handle selection + Expanded( + child: GestureDetector( + onTap: () { + if (widget.onItemSelected != null) { + widget.onItemSelected!(); + } + }, + child: Text( + _capitalizeFirstLetter(widget.title), + style: TextStyle( + color: widget.isSelected + ? ColorsManager.blackColor // Change color to black when selected + : ColorsManager.lightGrayColor, // Gray when not selected + fontWeight: FontWeight.w400, + ), + ), + ), + ), + ], + ), + // The expanded section (children) that shows when the tile is expanded + if (_isExpanded && widget.children != null && widget.children!.isNotEmpty) + Padding( + padding: const EdgeInsets.only(left: 48.0), // Indented children + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: widget.children!, + ), + ), + ], + ); + } +} diff --git a/lib/pages/space_tree/view/space_tile.dart b/lib/pages/space_tree/view/space_tile.dart new file mode 100644 index 00000000..12d20629 --- /dev/null +++ b/lib/pages/space_tree/view/space_tile.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/space_tree/view/custom_expansion.dart'; + +class SpaceTileSpaceTree extends StatefulWidget { + final String title; + final bool isSelected; + + final bool initiallyExpanded; + final ValueChanged onExpansionChanged; + final List? children; + final Function() onItemSelected; + + const SpaceTileSpaceTree({ + super.key, + required this.title, + required this.initiallyExpanded, + required this.onExpansionChanged, + required this.onItemSelected, + required this.isSelected, + this.children, + }); + + @override + _SpaceTileState createState() => _SpaceTileState(); +} + +class _SpaceTileState extends State { + late bool _isExpanded; + + @override + void initState() { + super.initState(); + _isExpanded = widget.initiallyExpanded; + } + + @override + Widget build(BuildContext context) { + return CustomExpansionTileSpaceTree( + isSelected: widget.isSelected, + title: widget.title, + initiallyExpanded: _isExpanded, + onItemSelected: widget.onItemSelected, + onExpansionChanged: (bool expanded) { + setState(() { + _isExpanded = expanded; + }); + widget.onExpansionChanged(expanded); + }, + children: widget.children ?? [], + ); + } +} diff --git a/lib/pages/space_tree/view/space_tree_view.dart b/lib/pages/space_tree/view/space_tree_view.dart index 59e7b73a..06e77df0 100644 --- a/lib/pages/space_tree/view/space_tree_view.dart +++ b/lib/pages/space_tree/view/space_tree_view.dart @@ -4,10 +4,10 @@ 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'; +import 'package:syncrow_web/pages/space_tree/view/community_tile.dart'; +import 'package:syncrow_web/pages/space_tree/view/space_tile.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/pages/spaces_management/all_spaces/widgets/community_tile.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -194,7 +194,7 @@ class _SpaceTreeViewState extends State { Widget _buildCommunityTile(BuildContext context, CommunityModel community) { bool hasChildren = community.spaces.isNotEmpty; - return CommunityTile( + return CommunityTileSpaceTree( title: community.name, key: ValueKey(community.uuid), isSelected: _selectedCommunityId == community.uuid, @@ -221,7 +221,7 @@ class _SpaceTreeViewState extends State { } Widget _buildSpaceTile(SpaceModel space, CommunityModel community) { - return SpaceTile( + return SpaceTileSpaceTree( title: space.name, key: ValueKey(space.uuid), isSelected: _selectedSpaceUuid == space.uuid, From e47f3d6d59d2982fa495ba2a6283ba7323a6359a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 21 Jan 2025 20:26:30 +0400 Subject: [PATCH 113/175] fixed space model creation --- .../views/assign_tag_models_dialog.dart | 37 ++++-- .../bloc/create_space_model_bloc.dart | 11 +- .../bloc/create_space_model_event.dart | 5 +- .../space_model/view/space_model_page.dart | 20 ++-- .../dialog/create_space_model_dialog.dart | 111 +++++++++--------- .../widgets/tag_chips_display_widget.dart | 49 ++++---- .../views/add_device_type_model_widget.dart | 37 +++--- 7 files changed, 144 insertions(+), 126 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 3a2499f9..006a5f7c 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -9,8 +9,10 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_pr import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_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/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -23,8 +25,8 @@ class AssignTagModelsDialog extends StatelessWidget { final List? allTags; final String spaceName; final String title; - final void Function( - List? tags, List? subspaces)? onUpdate; + final BuildContext? pageContext; + final List? otherSpaceModels; const AssignTagModelsDialog( {Key? key, @@ -36,7 +38,8 @@ class AssignTagModelsDialog extends StatelessWidget { this.allTags, required this.spaceName, required this.title, - this.onUpdate}) + this.pageContext, + this.otherSpaceModels}) : super(key: key); @override @@ -271,6 +274,8 @@ class AssignTagModelsDialog extends StatelessWidget { } } if (context.mounted) { + Navigator.of(context).pop(); + await showDialog( barrierDismissible: false, context: context, @@ -282,11 +287,9 @@ class AssignTagModelsDialog extends StatelessWidget { initialSelectedProducts: addedProducts, allTags: allTags, spaceName: spaceName, + otherSpaceModels: otherSpaceModels, spaceTagModels: state.tags, - onUpdate: (tags, subspaces) { - onUpdate?.call(state.tags, subspaces); - Navigator.of(context).pop(); - }, + pageContext: pageContext, ), ); } @@ -304,8 +307,6 @@ class AssignTagModelsDialog extends StatelessWidget { foregroundColor: ColorsManager.whiteColors, onPressed: state.isSaveEnabled ? () async { - Navigator.of(context).pop(); - for (var tag in state.tags) { if (tag.location == null || subspaces == null) { @@ -333,8 +334,24 @@ class AssignTagModelsDialog extends StatelessWidget { t.internalId == tag.internalId); } } + Navigator.of(context) + .popUntil((route) => route.isFirst); - onUpdate?.call(state.tags, subspaces); + await showDialog( + context: context, + builder: (BuildContext dialogContext) { + return CreateSpaceModelDialog( + products: products, + allTags: allTags, + pageContext: pageContext, + otherSpaceModels: otherSpaceModels, + spaceModel: SpaceTemplateModel( + modelName: spaceName, + tags: state.tags, + subspaceModels: subspaces), + ); + }, + ); } : null, child: const Text('Save'), diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 33defb52..417ba1fd 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; @@ -52,8 +54,6 @@ class CreateSpaceModelBloc } }); - - on((event, emit) { emit(CreateSpaceModelLoading()); Future.delayed(const Duration(seconds: 1), () { @@ -173,7 +173,12 @@ class CreateSpaceModelBloc on((event, emit) { final currentState = state; if (currentState is CreateSpaceModelLoaded) { - if (event.name.trim().isEmpty) { + if (event.allModels.contains(event.name) == true) { + emit(CreateSpaceModelLoaded( + currentState.space, + errorMessage: "Duplicate Model name", + )); + } else if (event.name.trim().isEmpty) { emit(CreateSpaceModelLoaded( currentState.space, errorMessage: "Model name cannot be empty", diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index 2bcb12b6..a78ae78f 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -34,11 +34,12 @@ class CreateSpaceTemplate extends CreateSpaceModelEvent { class UpdateSpaceTemplateName extends CreateSpaceModelEvent { final String name; + final List allModels; - UpdateSpaceTemplateName({required this.name}); + UpdateSpaceTemplateName({required this.name, required this.allModels}); @override - List get props => [name]; + List get props => [name,allModels]; } class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent { diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index d0b5a300..47bea955 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.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/bloc/space_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/add_space_model_widget.dart'; @@ -24,6 +23,7 @@ class SpaceModelPage extends StatelessWidget { } else if (state is SpaceModelLoaded) { final spaceModels = state.spaceModels; final allTagValues = _getAllTagValues(spaceModels); + final allSpaceModelNames = _getAllSpaceModelName(spaceModels); return Scaffold( backgroundColor: ColorsManager.whiteColors, @@ -52,12 +52,8 @@ class SpaceModelPage extends StatelessWidget { return CreateSpaceModelDialog( products: products, allTags: allTagValues, - onLoad: (newModel) { - context.read().add( - CreateSpaceModel( - newSpaceModel: newModel), - ); - }, + pageContext: context, + otherSpaceModels: allSpaceModelNames, ); }, ); @@ -76,7 +72,7 @@ class SpaceModelPage extends StatelessWidget { products: products, allTags: allTagValues, spaceModel: model, - onLoad: (newModel) {}, + otherSpaceModels: allSpaceModelNames, ); }, ); @@ -128,4 +124,12 @@ class SpaceModelPage extends StatelessWidget { } return allTags; } + + List _getAllSpaceModelName(List spaceModels) { + final List names = []; + for (final spaceModel in spaceModels) { + names.add(spaceModel.modelName); + } + return names; + } } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index a3f34e31..4c4b8a7c 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -8,6 +8,8 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_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/tag_chips_display_widget.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/widgets/subspace_model_create_widget.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; @@ -17,15 +19,17 @@ class CreateSpaceModelDialog extends StatelessWidget { final List? products; final List? allTags; final SpaceTemplateModel? spaceModel; - final void Function(SpaceTemplateModel newModel)? onLoad; + final BuildContext? pageContext; + final List? otherSpaceModels; - const CreateSpaceModelDialog({ - Key? key, - this.products, - this.allTags, - this.spaceModel, - this.onLoad, - }) : super(key: key); + const CreateSpaceModelDialog( + {Key? key, + this.products, + this.allTags, + this.spaceModel, + this.pageContext, + this.otherSpaceModels}) + : super(key: key); @override Widget build(BuildContext context) { @@ -44,17 +48,17 @@ class CreateSpaceModelDialog extends StatelessWidget { child: BlocProvider( create: (_) { final bloc = CreateSpaceModelBloc(_spaceModelApi); - if (spaceModel != null) { - bloc.add(UpdateSpaceTemplate(spaceModel!)); - } else { - bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( - modelName: '', - subspaceModels: const [], - ))); - } - + if (spaceModel != null) { + bloc.add(UpdateSpaceTemplate(spaceModel!)); + } else { + bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( + modelName: '', + subspaceModels: const [], + ))); + } + spaceNameController.addListener(() { - bloc.add(UpdateSpaceTemplateName(name: spaceNameController.text)); + bloc.add(UpdateSpaceTemplateName(name: spaceNameController.text,allModels: otherSpaceModels ??[])); }); return bloc; @@ -86,9 +90,10 @@ class CreateSpaceModelDialog extends StatelessWidget { child: TextField( controller: spaceNameController, onChanged: (value) { - context - .read() - .add(UpdateSpaceTemplateName(name: value)); + context.read().add( + UpdateSpaceTemplateName( + name: value, + allModels: otherSpaceModels ?? [])); }, style: const TextStyle(color: ColorsManager.blackColor), decoration: InputDecoration( @@ -128,21 +133,8 @@ class CreateSpaceModelDialog extends StatelessWidget { subspaces: subspaces, allTags: allTags, spaceNameController: spaceNameController, - onLoad: (tags, subspaces) { - if (context.read().state - is CreateSpaceModelLoaded) { - if (subspaces != null) { - context - .read() - .add(AddSubspacesToSpaceTemplate(subspaces)); - } - if (tags != null) { - context - .read() - .add(AddTagsToSpaceTemplate(tags)); - } - } - }, + pageContext: pageContext, + otherSpaceModels: otherSpaceModels, ), const SizedBox(height: 20), SizedBox( @@ -161,26 +153,35 @@ class CreateSpaceModelDialog extends StatelessWidget { onPressed: state.errorMessage == null || isNameValid ? () { - final updatedSpaceTemplate = - updatedSpaceModel.copyWith( - modelName: - spaceNameController.text.trim(), - ); - if(updatedSpaceTemplate.uuid != null){ - + if (updatedSpaceModel.uuid == null) { + final updatedSpaceTemplate = + updatedSpaceModel.copyWith( + modelName: + spaceNameController.text.trim(), + ); + if (updatedSpaceTemplate.uuid != + null) {} + + context + .read() + .add( + CreateSpaceTemplate( + spaceTemplate: + updatedSpaceTemplate, + onCreate: (newModel) { + if (pageContext != null) { + pageContext! + .read() + .add(CreateSpaceModel( + newSpaceModel: + newModel)); + } + Navigator.of(context) + .pop(); // Close the dialog + }, + ), + ); } - - context.read().add( - CreateSpaceTemplate( - spaceTemplate: - updatedSpaceTemplate, - onCreate: (newModel) { - onLoad!(newModel); - Navigator.of(context) - .pop(); // Close the dialog - }, - ), - ); } : null, backgroundColor: ColorsManager.secondaryColor, diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 18efd501..0c46076f 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -5,7 +5,6 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assi import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -17,8 +16,8 @@ class TagChipDisplay extends StatelessWidget { final List? subspaces; final List? allTags; final TextEditingController spaceNameController; - final void Function( - List? tags, List? subspaces)? onLoad; + final BuildContext? pageContext; + final List? otherSpaceModels; const TagChipDisplay(BuildContext context, {Key? key, @@ -28,7 +27,8 @@ class TagChipDisplay extends StatelessWidget { required this.subspaces, required this.allTags, required this.spaceNameController, - this.onLoad}) + this.pageContext, + this.otherSpaceModels}) : super(key: key); @override @@ -91,24 +91,22 @@ class TagChipDisplay extends StatelessWidget { if (navigatorContext != null) { await showDialog( - barrierDismissible: false, - context: navigatorContext, - builder: (context) => AssignTagModelsDialog( - products: products, - subspaces: subspaces, - allTags: allTags, - initialTags: TagHelper.generateInitialTags( + barrierDismissible: false, + context: navigatorContext, + builder: (context) => AssignTagModelsDialog( + products: products, subspaces: subspaces, - spaceTagModels: spaceModel?.tags ?? []), - title: 'Edit Device', - addedProducts: - TagHelper.createInitialSelectedProducts( - spaceModel?.tags ?? [], subspaces), - spaceName: spaceModel?.modelName ?? '', - onUpdate: (tags, subspaces) { - onLoad?.call(tags, subspaces); - }), - ); + pageContext: pageContext, + allTags: allTags, + initialTags: TagHelper.generateInitialTags( + subspaces: subspaces, + spaceTagModels: spaceModel?.tags ?? []), + title: 'Edit Device', + addedProducts: + TagHelper.createInitialSelectedProducts( + spaceModel?.tags ?? [], subspaces), + spaceName: spaceModel?.modelName ?? '', + )); } }, child: Chip( @@ -129,6 +127,8 @@ class TagChipDisplay extends StatelessWidget { ) : TextButton( onPressed: () async { + Navigator.of(context).pop(); + await showDialog( barrierDismissible: false, context: context, @@ -137,13 +137,8 @@ class TagChipDisplay extends StatelessWidget { subspaces: subspaces, allTags: allTags, spaceName: spaceNameController.text, + pageContext: pageContext, isCreate: true, - onUpdate: (tags, subspaces) { - onLoad?.call(tags, subspaces); - }, - onLoad: (tags, subspaces) { - onLoad?.call(tags, subspaces); - }, ), ); }, diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index 7944eb4f..ddb9b5a3 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; @@ -21,28 +22,23 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final List? allTags; final String spaceName; final bool isCreate; - final void Function( - List? tags, List? subspaces)? onLoad; - final void Function( - List? tags, List? subspaces)? onUpdate; + final List? otherSpaceModels; + final BuildContext? pageContext; - const AddDeviceTypeModelWidget({ - super.key, - this.products, - this.initialSelectedProducts, - this.subspaces, - this.allTags, - this.spaceTagModels, - required this.spaceName, - required this.isCreate, - this.onLoad, - this.onUpdate, - }); + const AddDeviceTypeModelWidget( + {super.key, + this.products, + this.initialSelectedProducts, + this.subspaces, + this.allTags, + this.spaceTagModels, + required this.spaceName, + required this.isCreate, + this.pageContext, + this.otherSpaceModels}); @override Widget build(BuildContext context) { - - final size = MediaQuery.of(context).size; final crossAxisCount = size.width > 1200 ? 8 @@ -142,10 +138,9 @@ class AddDeviceTypeModelWidget extends StatelessWidget { allTags: allTags, spaceName: spaceName, initialTags: state.initialTag, + otherSpaceModels: otherSpaceModels, title: dialogTitle, - onUpdate: (tags, subspaces) { - onUpdate?.call(tags, subspaces); - }, + pageContext: pageContext, ), ); } From 44d95f57011aeb3bb1ead79d9ad4e34b60afecc9 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 21 Jan 2025 20:29:15 +0400 Subject: [PATCH 114/175] fixed index --- .../assign_tag_models/views/assign_tag_models_dialog.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 006a5f7c..d345a8c6 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -133,7 +133,7 @@ class AssignTagModelsDialog extends StatelessWidget { return DataRow( cells: [ - DataCell(Text(index.toString())), + DataCell(Text((index + 1).toString())), DataCell( Row( mainAxisAlignment: From 18afc4f563767f5bc429065015d031f21ffbeb11 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 21 Jan 2025 20:37:21 +0400 Subject: [PATCH 115/175] fixed issues --- .../spaces_management/space_model/view/space_model_page.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 47bea955..33509998 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -63,6 +63,9 @@ class SpaceModelPage extends StatelessWidget { } // Render existing space model final model = spaceModels[index]; + final otherModel = List.from(allSpaceModelNames); + otherModel.remove(model.modelName); + return GestureDetector( onTap: () { showDialog( @@ -72,7 +75,7 @@ class SpaceModelPage extends StatelessWidget { products: products, allTags: allTagValues, spaceModel: model, - otherSpaceModels: allSpaceModelNames, + otherSpaceModels: otherModel, ); }, ); From 7ffdc67016ae96b49e06bb1fb629d5bc929677ca Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 22 Jan 2025 12:48:46 +0400 Subject: [PATCH 116/175] added product comparison --- .../all_spaces/model/product_model.dart | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/pages/spaces_management/all_spaces/model/product_model.dart b/lib/pages/spaces_management/all_spaces/model/product_model.dart index 557c106b..a4ebd550 100644 --- a/lib/pages/spaces_management/all_spaces/model/product_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/product_model.dart @@ -66,4 +66,25 @@ class ProductModel { String toString() { return 'ProductModel(uuid: $uuid, catName: $catName, prodId: $prodId, prodType: $prodType, name: $name, icon: $icon)'; } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ProductModel && + runtimeType == other.runtimeType && + uuid == other.uuid && + catName == other.catName && + prodId == other.prodId && + prodType == other.prodType && + name == other.name && + icon == other.icon; + + @override + int get hashCode => + uuid.hashCode ^ + catName.hashCode ^ + prodId.hashCode ^ + prodType.hashCode ^ + name.hashCode ^ + icon.hashCode; } From f35b699d4c7b2afb433e255d79db53a2ca185e46 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 22 Jan 2025 12:49:47 +0400 Subject: [PATCH 117/175] fixed the edit flow for space model --- .../widgets/device_type_tile_widget.dart | 1 + .../bloc/space_management_state.dart | 15 +- .../widgets/add_device_type_widget.dart | 1 + .../all_spaces/widgets/counter_widget.dart | 25 +- .../views/assign_tag_models_dialog.dart | 37 ++- .../views/create_subspace_model_dialog.dart | 6 +- .../spaces_management/helper/tag_helper.dart | 3 +- .../bloc/create_space_model_bloc.dart | 228 +++++++++++++++++- .../bloc/create_space_model_event.dart | 15 +- .../space_model/bloc/space_model_bloc.dart | 20 ++ .../space_model/bloc/space_model_event.dart | 18 ++ .../create_space_template_body_model.dart | 4 +- .../models/space_template_model.dart | 47 +--- .../space_model/models/tag_update_model.dart | 34 +++ .../space_model/view/space_model_page.dart | 2 +- .../dialog/create_space_model_dialog.dart | 64 ++++- .../widgets/subspace_chip_widget.dart | 3 - .../widgets/subspace_model_create_widget.dart | 34 +-- .../widgets/tag_chips_display_widget.dart | 2 + .../views/add_device_type_model_widget.dart | 47 +++- .../widgets/device_type_tile_widget.dart | 12 +- .../widgets/scrollable_grid_view_widget.dart | 5 +- lib/services/space_model_mang_api.dart | 14 ++ lib/utils/constants/api_const.dart | 7 +- 24 files changed, 517 insertions(+), 127 deletions(-) create mode 100644 lib/pages/spaces_management/space_model/models/tag_update_model.dart diff --git a/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart b/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart index 2feea7d9..08ad79ac 100644 --- a/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart +++ b/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart @@ -48,6 +48,7 @@ class DeviceTypeTileWidget extends StatelessWidget { DeviceNameWidget(name: product.name), const SizedBox(height: 4), CounterWidget( + isCreate: false, initialCount: selectedProduct.count, onCountChanged: (newCount) { context.read().add( diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart index 635c244d..571651e5 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_state.dart @@ -27,8 +27,7 @@ class SpaceManagementLoaded extends SpaceManagementState { required this.products, this.selectedCommunity, this.selectedSpace, - this.spaceModels - }); + this.spaceModels}); } class SpaceModelManagenetLoaded extends SpaceManagementState { @@ -38,14 +37,10 @@ class SpaceModelManagenetLoaded extends SpaceManagementState { class BlankState extends SpaceManagementState { final List communities; final List products; - List? spaceModels; + List? spaceModels; - - BlankState({ - required this.communities, - required this.products, - this.spaceModels - }); + BlankState( + {required this.communities, required this.products, this.spaceModels}); } class SpaceCreationSuccess extends SpaceManagementState { @@ -67,7 +62,7 @@ class SpaceManagementError extends SpaceManagementState { } class SpaceModelLoaded extends SpaceManagementState { - final List spaceModels; + List spaceModels; final List products; final List communities; diff --git a/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart index 351eacce..0e9f4bd1 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart @@ -137,6 +137,7 @@ class _AddDeviceWidgetState extends State { _buildDeviceName(product, size), const SizedBox(height: 4), CounterWidget( + isCreate: false, initialCount: selectedProduct.count, onCountChanged: (newCount) { setState(() { diff --git a/lib/pages/spaces_management/all_spaces/widgets/counter_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/counter_widget.dart index 66935b12..2289819b 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/counter_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/counter_widget.dart @@ -4,12 +4,14 @@ import 'package:syncrow_web/utils/color_manager.dart'; class CounterWidget extends StatefulWidget { final int initialCount; final ValueChanged onCountChanged; + final bool isCreate; - const CounterWidget({ - Key? key, - this.initialCount = 0, - required this.onCountChanged, - }) : super(key: key); + const CounterWidget( + {Key? key, + this.initialCount = 0, + required this.onCountChanged, + required this.isCreate}) + : super(key: key); @override State createState() => _CounterWidgetState(); @@ -53,25 +55,26 @@ class _CounterWidgetState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - _buildCounterButton(Icons.remove, _decrementCounter), + _buildCounterButton(Icons.remove, _decrementCounter,!widget.isCreate ), const SizedBox(width: 8), Text( '$_counter', - style: theme.textTheme.bodyLarge?.copyWith(color: ColorsManager.spaceColor), + style: theme.textTheme.bodyLarge + ?.copyWith(color: ColorsManager.spaceColor), ), const SizedBox(width: 8), - _buildCounterButton(Icons.add, _incrementCounter), + _buildCounterButton(Icons.add, _incrementCounter, false), ], ), ); } - Widget _buildCounterButton(IconData icon, VoidCallback onPressed) { + Widget _buildCounterButton(IconData icon, VoidCallback onPressed, bool isDisabled) { return GestureDetector( - onTap: onPressed, + onTap: isDisabled? null: onPressed, child: Icon( icon, - color: ColorsManager.spaceColor, + color: isDisabled? ColorsManager.spaceColor.withOpacity(0.3): ColorsManager.spaceColor, size: 18, ), ); diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index d345a8c6..fc778436 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -19,6 +19,8 @@ import 'package:syncrow_web/utils/color_manager.dart'; class AssignTagModelsDialog extends StatelessWidget { final List? products; final List? subspaces; + final SpaceTemplateModel? spaceModel; + final List initialTags; final ValueChanged>? onTagsAssigned; final List addedProducts; @@ -39,7 +41,8 @@ class AssignTagModelsDialog extends StatelessWidget { required this.spaceName, required this.title, this.pageContext, - this.otherSpaceModels}) + this.otherSpaceModels, + this.spaceModel}) : super(key: key); @override @@ -210,7 +213,7 @@ class AssignTagModelsDialog extends StatelessWidget { child: DialogDropdown( items: locations, selectedValue: - tag.location ?? 'None', + tag.location ?? 'Main Space', onSelected: (value) { context .read< @@ -281,16 +284,23 @@ class AssignTagModelsDialog extends StatelessWidget { context: context, builder: (dialogContext) => AddDeviceTypeModelWidget( - products: products, - subspaces: subspaces, - isCreate: false, - initialSelectedProducts: addedProducts, - allTags: allTags, - spaceName: spaceName, - otherSpaceModels: otherSpaceModels, - spaceTagModels: state.tags, - pageContext: pageContext, - ), + products: products, + subspaces: subspaces, + isCreate: false, + initialSelectedProducts: + addedProducts, + allTags: allTags, + spaceName: spaceName, + otherSpaceModels: otherSpaceModels, + spaceTagModels: state.tags, + pageContext: pageContext, + spaceModel: SpaceTemplateModel( + modelName: spaceName, + tags: state.tags, + uuid: spaceModel?.uuid, + internalId: + spaceModel?.internalId, + subspaceModels: subspaces)), ); } }, @@ -348,6 +358,9 @@ class AssignTagModelsDialog extends StatelessWidget { spaceModel: SpaceTemplateModel( modelName: spaceName, tags: state.tags, + uuid: spaceModel?.uuid, + internalId: + spaceModel?.internalId, subspaceModels: subspaces), ); }, 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 82aa3684..4c0cb99f 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 @@ -186,8 +186,7 @@ class CreateSubSpaceModelDialog extends StatelessWidget { const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: (state.subSpaces.isEmpty || - state.errorMessage.isNotEmpty) + onPressed: (state.errorMessage.isNotEmpty) ? null : () async { final subSpaces = context @@ -201,8 +200,7 @@ class CreateSubSpaceModelDialog extends StatelessWidget { }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, - foregroundColor: state.subSpaces.isEmpty || - state.errorMessage.isNotEmpty + foregroundColor: state.errorMessage.isNotEmpty ? ColorsManager.whiteColorsWithOpacity : ColorsManager.whiteColors, child: const Text('OK'), diff --git a/lib/pages/spaces_management/helper/tag_helper.dart b/lib/pages/spaces_management/helper/tag_helper.dart index bfff02a9..d4a0ea55 100644 --- a/lib/pages/spaces_management/helper/tag_helper.dart +++ b/lib/pages/spaces_management/helper/tag_helper.dart @@ -37,7 +37,8 @@ class TagHelper { final Map groupedTags = {}; for (var tag in tags) { if (tag.product != null) { - groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1; + final product = tag.product!; + groupedTags[product] = (groupedTags[product] ?? 0) + 1; } } return groupedTags; diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 417ba1fd..40db384a 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -1,12 +1,12 @@ -import 'dart:math'; - import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; +import 'package:syncrow_web/utils/constants/action_enum.dart'; class CreateSpaceModelBloc extends Bloc { @@ -193,5 +193,229 @@ class CreateSpaceModelBloc emit(CreateSpaceModelError("Space template not initialized")); } }); + + on((event, emit) async { + try { + final prevSpaceModel = event.spaceTemplate; + final newSpaceModel = event.updatedSpaceTemplate; + String? spaceModelName; + if (prevSpaceModel.modelName != newSpaceModel.modelName) { + spaceModelName = newSpaceModel.modelName; + } + List tagUpdates = []; + final List subspaceUpdates = []; + + tagUpdates = processTagUpdates(prevSpaceModel.tags, newSpaceModel.tags); + + if (prevSpaceModel.subspaceModels != null) { + for (var prevSubspace in prevSpaceModel.subspaceModels!) { + if (newSpaceModel.subspaceModels == null) { + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.delete, + uuid: prevSubspace.uuid, + )); + continue; + } + + final subspaceExistsInNew = newSpaceModel.subspaceModels! + .any((newSubspace) => newSubspace.uuid == prevSubspace.uuid); + if (!subspaceExistsInNew) { + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.delete, + uuid: prevSubspace.uuid, + )); + } + } + } + + if (newSpaceModel.subspaceModels != null) { + for (var newSubspaceModel in newSpaceModel.subspaceModels!) { + if (newSubspaceModel.uuid == null || + newSubspaceModel.uuid!.isEmpty) { + final List tagUpdatesInSubspace = []; + + if (newSubspaceModel.tags != null) { + for (var tag in newSubspaceModel.tags!) { + tagUpdatesInSubspace.add(TagModelUpdate( + action: Action.add, + uuid: tag.uuid, + tag: tag.tag, + productUuid: tag.product?.uuid, + )); + } + } + + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.add, + subspaceName: newSubspaceModel.subspaceName, + tags: tagUpdatesInSubspace, + )); + } + } + } + + if (newSpaceModel.subspaceModels != null && + prevSpaceModel.subspaceModels != null) { + final prevSubspaceMap = { + for (var subspace in prevSpaceModel.subspaceModels!) + subspace.uuid: subspace + }; + + for (var newSubspace in newSpaceModel.subspaceModels!) { + if (newSubspace.uuid != null && + prevSubspaceMap.containsKey(newSubspace.uuid)) { + final prevSubspace = prevSubspaceMap[newSubspace.uuid]!; + + // Check if modelName has changed + if (newSubspace.subspaceName != prevSubspace.subspaceName) { + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.update, + uuid: newSubspace.uuid, + subspaceName: newSubspace.subspaceName, + )); + } + + // Compare tags within the subspace + final List tagUpdatesInSubspace = []; + if (prevSubspace.tags != null && newSubspace.tags != null) { + final prevTagMap = { + for (var tag in prevSubspace.tags!) tag.uuid: tag + }; + + // Check for deleted tags + for (var prevTag in prevSubspace.tags!) { + if (!newSubspace.tags! + .any((newTag) => newTag.uuid == prevTag.uuid)) { + tagUpdatesInSubspace.add(TagModelUpdate( + action: Action.delete, + uuid: prevTag.uuid, + )); + } + } + + // Check for added tags, including those without a UUID + for (var newTag in newSubspace.tags!) { + if (newTag.uuid == null || newTag.uuid!.isEmpty) { + // Add new tag without UUID + tagUpdatesInSubspace.add(TagModelUpdate( + action: Action.add, + uuid: null, // or generate a new UUID if required + tag: newTag.tag, + productUuid: newTag.product?.uuid, + )); + } else if (!prevSubspace.tags! + .any((prevTag) => prevTag.uuid == newTag.uuid)) { + // Add new tag with UUID + tagUpdatesInSubspace.add(TagModelUpdate( + action: Action.add, + uuid: newTag.uuid, + tag: newTag.tag, + productUuid: newTag.product?.uuid, + )); + } + } + + // Check for updated tags + for (var prevTag in prevSubspace.tags!) { + final newTag = newSubspace.tags!.cast().firstWhere( + (tag) => tag?.uuid == prevTag.uuid, + orElse: () => null, + ); + if (newTag != null && newTag.tag != prevTag.tag) { + tagUpdatesInSubspace.add(TagModelUpdate( + action: Action.update, + uuid: newTag.uuid, + tag: newTag.tag, + )); + } + } + } + + // Add the subspace with updated tags if necessary + if (tagUpdatesInSubspace.isNotEmpty) { + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.update, + uuid: newSubspace.uuid, + subspaceName: newSubspace.subspaceName, + tags: tagUpdatesInSubspace, + )); + } + } + } + } + + final spaceModelBody = CreateSpaceTemplateBodyModel( + modelName: spaceModelName, + tags: tagUpdates, + subspaceModels: subspaceUpdates); + + final res = await _api.updateSpaceModel( + spaceModelBody, prevSpaceModel.uuid ?? ''); + + if (res != null) { + emit(CreateSpaceModelLoaded(newSpaceModel)); + if (event.onUpdate != null) { + event.onUpdate!(event.updatedSpaceTemplate); + } + } + } catch (e) { + emit(CreateSpaceModelError('Error creating space model')); + } + }); + } + + List processTagUpdates( + List? prevTags, + List? newTags, + ) { + final List tagUpdates = []; + final processedTags = {}; + + if (newTags != null || prevTags != null) { + // Case 1: Tags deleted + if (prevTags != null && newTags != null) { + for (var prevTag in prevTags!) { + final existsInNew = + newTags!.any((newTag) => newTag.uuid == prevTag.uuid); + if (!existsInNew) { + tagUpdates + .add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid)); + } + } + } + + // Case 2: Tags added + if (newTags != null) { + for (var newTag in newTags!) { + // Tag without UUID + if ((newTag.uuid == null || newTag.uuid!.isEmpty) && + !processedTags.contains(newTag.tag)) { + tagUpdates.add(TagModelUpdate( + action: Action.add, + tag: newTag.tag, + productUuid: newTag.product?.uuid)); + processedTags.add(newTag.tag); + } + } + } + + // Case 3: Tags updated + if (prevTags != null && newTags != null) { + final newTagMap = {for (var tag in newTags!) tag.uuid: tag}; + + for (var prevTag in prevTags!) { + final newTag = newTagMap[prevTag.uuid]; + if (newTag != null) { + tagUpdates.add(TagModelUpdate( + action: Action.update, + uuid: newTag.uuid, + tag: newTag.tag, + )); + } else {} + } + } + } + + return tagUpdates; } } diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index a78ae78f..22828941 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -22,7 +22,6 @@ class CreateSpaceTemplate extends CreateSpaceModelEvent { final SpaceTemplateModel spaceTemplate; final Function(SpaceTemplateModel)? onCreate; - const CreateSpaceTemplate({ required this.spaceTemplate, this.onCreate, @@ -39,7 +38,7 @@ class UpdateSpaceTemplateName extends CreateSpaceModelEvent { UpdateSpaceTemplateName({required this.name, required this.allModels}); @override - List get props => [name,allModels]; + List get props => [name, allModels]; } class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent { @@ -54,9 +53,19 @@ class AddTagsToSpaceTemplate extends CreateSpaceModelEvent { AddTagsToSpaceTemplate(this.tags); } - class ValidateSpaceTemplateName extends CreateSpaceModelEvent { final String name; ValidateSpaceTemplateName({required this.name}); } + +class ModifySpaceTemplate extends CreateSpaceModelEvent { + final SpaceTemplateModel spaceTemplate; + final SpaceTemplateModel updatedSpaceTemplate; + final Function(SpaceTemplateModel)? onUpdate; + + ModifySpaceTemplate( + {required this.spaceTemplate, + required this.updatedSpaceTemplate, + this.onUpdate}); +} diff --git a/lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart index e383610d..090dfa13 100644 --- a/lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/space_model_bloc.dart @@ -12,6 +12,7 @@ class SpaceModelBloc extends Bloc { required List initialSpaceModels, }) : super(SpaceModelLoaded(spaceModels: initialSpaceModels)) { on(_onCreateSpaceModel); + on(_onUpdateSpaceModel); } Future _onCreateSpaceModel( @@ -33,4 +34,23 @@ class SpaceModelBloc extends Bloc { } } } + + Future _onUpdateSpaceModel( + UpdateSpaceModel event, Emitter emit) async { + final currentState = state; + if (currentState is SpaceModelLoaded) { + try { + final newSpaceModel = + await api.getSpaceModel(event.spaceModelUuid ?? ''); + if (newSpaceModel != null) { + final updatedSpaceModels = currentState.spaceModels.map((model) { + return model.uuid == event.spaceModelUuid ? newSpaceModel : model; + }).toList(); + emit(SpaceModelLoaded(spaceModels: updatedSpaceModels)); + } + } catch (e) { + emit(SpaceModelError(message: e.toString())); + } + } + } } diff --git a/lib/pages/spaces_management/space_model/bloc/space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/space_model_event.dart index 78331f3c..8f71e611 100644 --- a/lib/pages/spaces_management/space_model/bloc/space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/space_model_event.dart @@ -16,3 +16,21 @@ class CreateSpaceModel extends SpaceModelEvent { @override List get props => [newSpaceModel]; } + +class GetSpaceModel extends SpaceModelEvent { + final String spaceModelUuid; + + GetSpaceModel({required this.spaceModelUuid}); + + @override + List get props => [spaceModelUuid]; +} + +class UpdateSpaceModel extends SpaceModelEvent { + final String spaceModelUuid; + + UpdateSpaceModel({required this.spaceModelUuid}); + + @override + List get props => [spaceModelUuid]; +} diff --git a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart index e481a8b8..cb8d0aac 100644 --- a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart +++ b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart @@ -30,12 +30,12 @@ class CreateSubspaceTemplateModel { } class CreateSpaceTemplateBodyModel { - final String modelName; + final String? modelName; final List? tags; final List? subspaceModels; CreateSpaceTemplateBodyModel({ - required this.modelName, + this.modelName, this.tags, this.subspaceModels, }); 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 84f568a5..5edf912f 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 @@ -2,6 +2,7 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart'; import 'package:uuid/uuid.dart'; @@ -70,14 +71,14 @@ class SpaceTemplateModel extends Equatable { } class UpdateSubspaceTemplateModel { - final String uuid; + final String? uuid; final Action action; final String? subspaceName; - final List? tags; + final List? tags; UpdateSubspaceTemplateModel({ required this.action, - required this.uuid, + this.uuid, this.subspaceName, this.tags, }); @@ -88,7 +89,7 @@ class UpdateSubspaceTemplateModel { uuid: json['uuid'] ?? '', subspaceName: json['subspaceName'] ?? '', tags: (json['tags'] as List) - .map((item) => UpdateTagModel.fromJson(item)) + .map((item) => TagModelUpdate.fromJson(item)) .toList(), ); } @@ -103,44 +104,6 @@ class UpdateSubspaceTemplateModel { } } -class UpdateTagModel { - final Action action; - final String? uuid; - final String tag; - final bool disabled; - final ProductModel? product; - - UpdateTagModel({ - required this.action, - this.uuid, - required this.tag, - required this.disabled, - this.product, - }); - - factory UpdateTagModel.fromJson(Map json) { - return UpdateTagModel( - action: ActionExtension.fromValue(json['action']), - uuid: json['uuid'] ?? '', - tag: json['tag'] ?? '', - disabled: json['disabled'] ?? false, - product: json['product'] != null - ? ProductModel.fromMap(json['product']) - : null, - ); - } - - Map toJson() { - return { - 'action': action.value, - 'uuid': uuid, - 'tag': tag, - 'disabled': disabled, - 'product': product?.toMap(), - }; - } -} - extension SpaceTemplateExtensions on SpaceTemplateModel { List listAllTagValues() { final List tagValues = []; diff --git a/lib/pages/spaces_management/space_model/models/tag_update_model.dart b/lib/pages/spaces_management/space_model/models/tag_update_model.dart new file mode 100644 index 00000000..c7190dc8 --- /dev/null +++ b/lib/pages/spaces_management/space_model/models/tag_update_model.dart @@ -0,0 +1,34 @@ +import 'package:syncrow_web/utils/constants/action_enum.dart'; + +class TagModelUpdate { + final Action action; + final String? uuid; + final String? tag; + final String? productUuid; + + TagModelUpdate({ + required this.action, + this.uuid, + this.tag, + this.productUuid, + }); + + factory TagModelUpdate.fromJson(Map json) { + return TagModelUpdate( + action: json['action'], + uuid: json['uuid'], + tag: json['tag'], + productUuid: json['productUuid'], + ); + } + + // Method to convert an instance to JSON + Map toJson() { + return { + 'action': action.value, + 'uuid': uuid, // Nullable field + 'tag': tag, + 'productUuid': productUuid, + }; + } +} diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 33509998..ae623e81 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -65,7 +65,6 @@ class SpaceModelPage extends StatelessWidget { final model = spaceModels[index]; final otherModel = List.from(allSpaceModelNames); otherModel.remove(model.modelName); - return GestureDetector( onTap: () { showDialog( @@ -76,6 +75,7 @@ class SpaceModelPage extends StatelessWidget { allTags: allTagValues, spaceModel: model, otherSpaceModels: otherModel, + pageContext: context, ); }, ); diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 4c4b8a7c..c1bea0fd 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -6,6 +6,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_mod import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_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/tag_chips_display_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart'; @@ -58,7 +59,9 @@ class CreateSpaceModelDialog extends StatelessWidget { } spaceNameController.addListener(() { - bloc.add(UpdateSpaceTemplateName(name: spaceNameController.text,allModels: otherSpaceModels ??[])); + bloc.add(UpdateSpaceTemplateName( + name: spaceNameController.text, + allModels: otherSpaceModels ?? [])); }); return bloc; @@ -153,15 +156,12 @@ class CreateSpaceModelDialog extends StatelessWidget { onPressed: state.errorMessage == null || isNameValid ? () { + final updatedSpaceTemplate = + updatedSpaceModel.copyWith( + modelName: + spaceNameController.text.trim(), + ); if (updatedSpaceModel.uuid == null) { - final updatedSpaceTemplate = - updatedSpaceModel.copyWith( - modelName: - spaceNameController.text.trim(), - ); - if (updatedSpaceTemplate.uuid != - null) {} - context .read() .add( @@ -181,6 +181,52 @@ class CreateSpaceModelDialog extends StatelessWidget { }, ), ); + } else { + if (pageContext != null) { + final currentState = pageContext! + .read() + .state; + if (currentState + is SpaceModelLoaded) { + final spaceModels = + List.from( + currentState.spaceModels); + + final SpaceTemplateModel? + currentSpaceModel = spaceModels + .cast() + .firstWhere( + (sm) => + sm?.uuid == + updatedSpaceModel + .uuid, + orElse: () => null, + ); + if (currentSpaceModel != null) { + context + .read() + .add(ModifySpaceTemplate( + spaceTemplate: + currentSpaceModel, + updatedSpaceTemplate: + updatedSpaceTemplate, + onUpdate: (newModel) { + if (pageContext != + null) { + pageContext! + .read< + SpaceModelBloc>() + .add(UpdateSpaceModel( + spaceModelUuid: + newModel.uuid ?? + '')); + } + Navigator.of(context) + .pop(); + })); + } + } + } } } : null, diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart index 8f987c51..70ac6e24 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart @@ -1,9 +1,6 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:flutter/material.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; - class SubspaceChipWidget extends StatelessWidget { final String subspace; diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 7781bb5e..0dda53a6 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -46,23 +46,23 @@ class SubspaceModelCreate extends StatelessWidget { spacing: 8.0, runSpacing: 8.0, children: [ - ...subspaces.map( - (subspace) => Chip( - label: Text( - subspace.subspaceName, - style: const TextStyle( - color: ColorsManager.spaceColor), // Text color - ), - backgroundColor: - ColorsManager.whiteColors, // Chip background color - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(16), // Rounded chip - side: const BorderSide( - color: ColorsManager.spaceColor), // Border color - ), - ), - ), + ...subspaces.map((subspace) => Container( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, vertical: 4.0), + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: ColorsManager.transparentColor), + ), + child: Text( + subspace.subspaceName, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.spaceColor), + ), + )), GestureDetector( onTap: () async { await _openDialog(context, 'Edit Sub-space'); diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 0c46076f..d4111031 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -98,6 +98,7 @@ class TagChipDisplay extends StatelessWidget { subspaces: subspaces, pageContext: pageContext, allTags: allTags, + spaceModel: spaceModel, initialTags: TagHelper.generateInitialTags( subspaces: subspaces, spaceTagModels: spaceModel?.tags ?? []), @@ -139,6 +140,7 @@ class TagChipDisplay extends StatelessWidget { spaceName: spaceNameController.text, pageContext: pageContext, isCreate: true, + spaceModel: spaceModel, ), ); }, diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index ddb9b5a3..a9d40147 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -5,9 +5,10 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; -import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart'; @@ -24,6 +25,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final bool isCreate; final List? otherSpaceModels; final BuildContext? pageContext; + final SpaceTemplateModel? spaceModel; const AddDeviceTypeModelWidget( {super.key, @@ -35,7 +37,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { required this.spaceName, required this.isCreate, this.pageContext, - this.otherSpaceModels}); + this.otherSpaceModels, + this.spaceModel}); @override Widget build(BuildContext context) { @@ -75,6 +78,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 20.0), child: ScrollableGridViewWidget( + isCreate: isCreate, products: products, crossAxisCount: crossAxisCount, initialProductCounts: state.selectedProducts, @@ -98,6 +102,44 @@ class AddDeviceTypeModelWidget extends StatelessWidget { onPressed: () async { if (isCreate) { Navigator.of(context).pop(); + await showDialog( + context: context, + builder: (BuildContext dialogContext) { + return CreateSpaceModelDialog( + products: products, + allTags: allTags, + pageContext: pageContext, + otherSpaceModels: otherSpaceModels, + spaceModel: SpaceTemplateModel( + modelName: spaceName, + tags: spaceModel?.tags ?? [], + uuid: spaceModel?.uuid, + internalId: spaceModel?.internalId, + subspaceModels: subspaces), + ); + }, + ); + } else { + final initialTags = generateInitialTags( + spaceTagModels: spaceTagModels, + subspaces: subspaces, + ); + + Navigator.of(context).pop(); + await showDialog( + context: context, + builder: (context) => AssignTagModelsDialog( + products: products, + subspaces: subspaces, + addedProducts: initialSelectedProducts ?? [], + allTags: allTags, + spaceName: spaceName, + initialTags: initialTags, + otherSpaceModels: otherSpaceModels, + title: 'Edit Device', + spaceModel: spaceModel, + pageContext: pageContext, + )); } }, ), @@ -140,6 +182,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { initialTags: state.initialTag, otherSpaceModels: otherSpaceModels, title: dialogTitle, + spaceModel: spaceModel, pageContext: pageContext, ), ); diff --git a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart index c2d38d0b..7d103cdb 100644 --- a/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart +++ b/lib/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart @@ -13,12 +13,13 @@ import 'package:syncrow_web/utils/constants/assets.dart'; class DeviceTypeTileWidget extends StatelessWidget { final ProductModel product; final List productCounts; + final bool isCreate; - const DeviceTypeTileWidget({ - super.key, - required this.product, - required this.productCounts, - }); + const DeviceTypeTileWidget( + {super.key, + required this.product, + required this.productCounts, + required this.isCreate}); @override Widget build(BuildContext context) { @@ -48,6 +49,7 @@ class DeviceTypeTileWidget extends StatelessWidget { DeviceNameWidget(name: product.name), const SizedBox(height: 4), CounterWidget( + isCreate: isCreate, initialCount: selectedProduct.count, onCountChanged: (newCount) { context.read().add( diff --git a/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart b/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart index 3e32ccd8..d1775c66 100644 --- a/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart +++ b/lib/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart @@ -10,12 +10,14 @@ class ScrollableGridViewWidget extends StatelessWidget { final List? products; final int crossAxisCount; final List? initialProductCounts; + final bool isCreate; const ScrollableGridViewWidget({ super.key, required this.products, required this.crossAxisCount, this.initialProductCounts, + required this.isCreate }); @override @@ -30,7 +32,7 @@ class ScrollableGridViewWidget extends StatelessWidget { final productCounts = state is AddDeviceModelLoaded ? state.selectedProducts : []; - + return GridView.builder( controller: scrollController, shrinkWrap: true, @@ -47,6 +49,7 @@ class ScrollableGridViewWidget extends StatelessWidget { return DeviceTypeTileWidget( product: product, + isCreate: isCreate, productCounts: initialProductCount != null ? [...productCounts, initialProductCount] : productCounts, diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart index ee241189..eb896432 100644 --- a/lib/services/space_model_mang_api.dart +++ b/lib/services/space_model_mang_api.dart @@ -34,6 +34,20 @@ class SpaceModelManagementApi { return response; } + + Future updateSpaceModel( + CreateSpaceTemplateBodyModel spaceModel, String spaceModelUuid) async { + final response = await HTTPService().put( + path: ApiEndpoints.updateSpaceModel + .replaceAll('{projectId}', TempConst.projectId).replaceAll('{spaceModelUuid}', spaceModelUuid), + body: spaceModel.toJson(), + expectedResponseModel: (json) { + return json['message']; + }, + ); + return response; + } + Future getSpaceModel(String spaceModelUuid) async { final response = await HTTPService().get( path: ApiEndpoints.getSpaceModel diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 92a2581c..35020151 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -101,8 +101,11 @@ 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 getSpaceModel = + '/projects/{projectId}/space-models/{spaceModelUuid}'; + static const String updateSpaceModel = + '/projects/{projectId}/space-models/{spaceModelUuid}'; + static const String roleTypes = '/role/types'; static const String permission = '/permission/{roleUuid}'; static const String inviteUser = '/invite-user'; From 513175ed1e99b48ef28318243b1707316cba4e95 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 22 Jan 2025 15:44:46 +0300 Subject: [PATCH 118/175] user_agreement_dialog --- lib/pages/auth/model/user_model.dart | 38 ++++ lib/pages/home/bloc/home_bloc.dart | 40 ++++ lib/pages/home/bloc/home_event.dart | 6 +- lib/pages/home/bloc/home_state.dart | 6 + .../view/agreement_and_privacy_dialog.dart | 176 ++++++++++++++++++ lib/pages/home/view/home_page_web.dart | 36 +++- lib/services/home_api.dart | 29 +++ lib/utils/constants/api_const.dart | 5 +- pubspec.yaml | 2 + 9 files changed, 331 insertions(+), 7 deletions(-) create mode 100644 lib/pages/home/view/agreement_and_privacy_dialog.dart diff --git a/lib/pages/auth/model/user_model.dart b/lib/pages/auth/model/user_model.dart index 84d4661f..5090b0e0 100644 --- a/lib/pages/auth/model/user_model.dart +++ b/lib/pages/auth/model/user_model.dart @@ -10,6 +10,10 @@ class UserModel { final String? phoneNumber; final bool? isEmailVerified; final bool? isAgreementAccepted; + final bool? hasAcceptedWebAgreement; + final DateTime? webAgreementAcceptedAt; + final UserRole? role; + UserModel({ required this.uuid, required this.email, @@ -19,6 +23,9 @@ class UserModel { required this.phoneNumber, required this.isEmailVerified, required this.isAgreementAccepted, + required this.hasAcceptedWebAgreement, + required this.webAgreementAcceptedAt, + required this.role, }); factory UserModel.fromJson(Map json) { @@ -31,6 +38,11 @@ class UserModel { phoneNumber: json['phoneNumber'], isEmailVerified: json['isEmailVerified'], isAgreementAccepted: json['isAgreementAccepted'], + hasAcceptedWebAgreement: json['hasAcceptedWebAgreement'], + webAgreementAcceptedAt: json['webAgreementAcceptedAt'] != null + ? DateTime.parse(json['webAgreementAcceptedAt']) + : null, + role: json['role'] != null ? UserRole.fromJson(json['role']) : null, ); } @@ -41,6 +53,9 @@ class UserModel { Map tempJson = Token.decodeToken(token.accessToken); return UserModel( + hasAcceptedWebAgreement: null, + role: null, + webAgreementAcceptedAt: null, uuid: tempJson['uuid'].toString(), email: tempJson['email'], firstName: null, @@ -65,3 +80,26 @@ class UserModel { }; } } + +class UserRole { + final String uuid; + final DateTime createdAt; + final DateTime updatedAt; + final String type; + + UserRole({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.type, + }); + + factory UserRole.fromJson(Map json) { + return UserRole( + uuid: json['uuid'], + createdAt: DateTime.parse(json['createdAt']), + updatedAt: DateTime.parse(json['updatedAt']), + type: json['type'], + ); + } +} diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index 416e9d92..8c887810 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -18,10 +18,15 @@ class HomeBloc extends Bloc { List sourcesList = []; List destinationsList = []; UserModel? user; + String terms = ''; + String policy = ''; HomeBloc() : super((HomeInitial())) { on(_createNode); on(_fetchUserInfo); + on(_fetchTerms); + on(_fetchPolicy); + on(_confirmUserAgreement); } void _createNode(CreateNewNode event, Emitter emit) async { @@ -45,12 +50,47 @@ class HomeBloc extends Bloc { var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey); user = await HomeApi().fetchUserInfo(uuid); + add(FetchTermEvent()); + add(FetchPolicyEvent()); emit(HomeInitial()); } catch (e) { return; } } + Future _fetchTerms(FetchTermEvent event, Emitter emit) async { + try { + emit(LoadingHome()); + terms = await HomeApi().fetchTerms(); + emit(TermsAgreement()); + } catch (e) { + return; + } + } + + Future _fetchPolicy(FetchPolicyEvent event, Emitter emit) async { + try { + emit(LoadingHome()); + policy = await HomeApi().fetchPolicy(); + emit(PolicyAgreement()); + } catch (e) { + return; + } + } + + Future _confirmUserAgreement( + ConfirmUserAgreementEvent event, Emitter emit) async { + try { + emit(LoadingHome()); + var uuid = + await const FlutterSecureStorage().read(key: UserModel.userUuidKey); + policy = await HomeApi().confirmUserAgreements(uuid); + emit(PolicyAgreement()); + } catch (e) { + return; + } + } + // static Future fetchUserInfo() async { // try { // var uuid = diff --git a/lib/pages/home/bloc/home_event.dart b/lib/pages/home/bloc/home_event.dart index 963202b9..91b3bee8 100644 --- a/lib/pages/home/bloc/home_event.dart +++ b/lib/pages/home/bloc/home_event.dart @@ -20,4 +20,8 @@ class CreateNewNode extends HomeEvent { class FetchUserInfo extends HomeEvent { const FetchUserInfo(); -} \ No newline at end of file +}class FetchTermEvent extends HomeEvent {} + +class FetchPolicyEvent extends HomeEvent {} + +class ConfirmUserAgreementEvent extends HomeEvent {} \ No newline at end of file diff --git a/lib/pages/home/bloc/home_state.dart b/lib/pages/home/bloc/home_state.dart index 10c50486..5640d550 100644 --- a/lib/pages/home/bloc/home_state.dart +++ b/lib/pages/home/bloc/home_state.dart @@ -7,8 +7,12 @@ abstract class HomeState extends Equatable { @override List get props => []; } +class LoadingHome extends HomeState {} class HomeInitial extends HomeState {} +class TermsAgreement extends HomeState {} + +class PolicyAgreement extends HomeState {} class HomeCounterState extends HomeState { final int counter; @@ -24,3 +28,5 @@ class HomeUpdateTree extends HomeState { @override List get props => [graph, builder]; } + +//FetchTermEvent \ No newline at end of file diff --git a/lib/pages/home/view/agreement_and_privacy_dialog.dart b/lib/pages/home/view/agreement_and_privacy_dialog.dart new file mode 100644 index 00000000..e9371ae9 --- /dev/null +++ b/lib/pages/home/view/agreement_and_privacy_dialog.dart @@ -0,0 +1,176 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:go_router/go_router.dart'; +import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/routes_const.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class AgreementAndPrivacyDialog extends StatefulWidget { + final String terms; + final String policy; + + const AgreementAndPrivacyDialog({ + super.key, + required this.terms, + required this.policy, + }); + + @override + _AgreementAndPrivacyDialogState createState() => + _AgreementAndPrivacyDialogState(); +} + +class _AgreementAndPrivacyDialogState extends State { + final ScrollController _scrollController = ScrollController(); + bool _isAtEnd = false; + int _currentPage = 1; + + @override + void initState() { + super.initState(); + _scrollController.addListener(_onScroll); + WidgetsBinding.instance + .addPostFrameCallback((_) => _checkScrollRequirement()); + } + + void _checkScrollRequirement() { + final scrollPosition = _scrollController.position; + if (scrollPosition.maxScrollExtent <= 0) { + setState(() { + _isAtEnd = true; + }); + } + } + + @override + void dispose() { + _scrollController.removeListener(_onScroll); + _scrollController.dispose(); + super.dispose(); + } + + void _onScroll() { + if (_scrollController.position.atEdge) { + final isAtBottom = _scrollController.position.pixels == + _scrollController.position.maxScrollExtent; + if (isAtBottom && !_isAtEnd) { + setState(() { + _isAtEnd = true; + }); + } + } + } + + String get _dialogTitle => + _currentPage == 2 ? 'User Agreement' : 'Privacy Policy'; + + String get _dialogContent => _currentPage == 2 ? widget.terms : widget.policy; + + Widget _buildScrollableContent() { + return Container( + padding: const EdgeInsets.all(40), + width: MediaQuery.of(context).size.width * 0.8, + height: MediaQuery.of(context).size.height * 0.75, + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + child: Scrollbar( + thumbVisibility: true, + trackVisibility: true, + interactive: true, + controller: _scrollController, + child: SingleChildScrollView( + controller: _scrollController, + padding: const EdgeInsets.all(25), + child: Html( + data: _dialogContent, + onLinkTap: (url, attributes, element) async { + if (url != null) { + final uri = Uri.parse(url); + await launchUrl(uri, mode: LaunchMode.externalApplication); + } + }, + style: { + "body": Style( + fontSize: FontSize(14), + color: Colors.black87, + lineHeight: LineHeight(1.5), + ), + }, + ), + ), + ), + ); + } + + Widget _buildActionButton() { + final String buttonText = _currentPage == 2 ? "I Agree" : "Next"; + + return InkWell( + onTap: _isAtEnd + ? () { + if (_currentPage == 1) { + setState(() { + _currentPage = 2; + _isAtEnd = false; + _scrollController.jumpTo(0); + WidgetsBinding.instance + .addPostFrameCallback((_) => _checkScrollRequirement()); + }); + } else { + Navigator.of(context).pop(true); + } + } + : null, + child: Text( + buttonText, + style: TextStyle( + color: _isAtEnd ? ColorsManager.secondaryColor : Colors.grey, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Dialog( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + _dialogTitle, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: ColorsManager.secondaryColor, + ), + ), + ), + const Divider(), + _buildScrollableContent(), + const Divider(), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + InkWell( + onTap: () { + AuthBloc.logout(); + context.go(RoutesConst.auth); + }, + child: const Text("Cancel"), + ), + _buildActionButton(), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/home/view/home_page_web.dart b/lib/pages/home/view/home_page_web.dart index a198fa76..8e7225d2 100644 --- a/lib/pages/home/view/home_page_web.dart +++ b/lib/pages/home/view/home_page_web.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/home/bloc/home_event.dart'; +import 'package:syncrow_web/pages/home/view/agreement_and_privacy_dialog.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_state.dart'; import 'package:syncrow_web/pages/home/view/home_card.dart'; @@ -9,16 +11,40 @@ import 'package:syncrow_web/web_layout/web_scaffold.dart'; class HomeWebPage extends StatelessWidget { const HomeWebPage({super.key}); + @override Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; + final homeBloc = BlocProvider.of(context); + return PopScope( canPop: false, onPopInvoked: (didPop) => false, child: BlocConsumer( - listener: (BuildContext context, state) {}, + listener: (BuildContext context, state) { + if (state is HomeInitial) { + if (homeBloc.user!.hasAcceptedWebAgreement == false) { + Future.delayed(const Duration(seconds: 1), () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AgreementAndPrivacyDialog( + terms: homeBloc.terms, + policy: homeBloc.policy, + ); + }, + ).then((v) { + if (v != null) { + homeBloc.add(ConfirmUserAgreementEvent()); + homeBloc.add(const FetchUserInfo()); + } + }); + }); + } + } + }, builder: (context, state) { - final homeBloc = BlocProvider.of(context); return WebScaffold( enableMenuSidebar: false, appBarTitle: Row( @@ -52,7 +78,8 @@ class HomeWebPage extends StatelessWidget { width: size.width * 0.68, child: GridView.builder( itemCount: 3, //8 - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 4, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, @@ -64,7 +91,8 @@ class HomeWebPage extends StatelessWidget { active: homeBloc.homeItems[index].active!, name: homeBloc.homeItems[index].title!, img: homeBloc.homeItems[index].icon!, - onTap: () => homeBloc.homeItems[index].onPress(context), + onTap: () => + homeBloc.homeItems[index].onPress(context), ); }, ), diff --git a/lib/services/home_api.dart b/lib/services/home_api.dart index dfbaf4bf..c1e67add 100644 --- a/lib/services/home_api.dart +++ b/lib/services/home_api.dart @@ -12,4 +12,33 @@ class HomeApi { }); return response; } + + Future fetchTerms() async { + final response = await HTTPService().get( + path: ApiEndpoints.terms, + showServerMessage: true, + expectedResponseModel: (json) { + return json['data']; + }); + return response; + } + + Future fetchPolicy() async { + final response = await HTTPService().get( + path: ApiEndpoints.policy, + showServerMessage: true, + expectedResponseModel: (json) { + return json['data']; + }); + return response; + } + + Future confirmUserAgreements(uuid) async { + final response = await HTTPService().patch( + path: ApiEndpoints.userAgreements.replaceAll('{userUuid}', uuid!), + expectedResponseModel: (json) { + return json['data']; + }); + return response; + } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index d0331dac..454ec4e7 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -108,6 +108,7 @@ abstract class ApiEndpoints { static const String deleteUser = '/invite-user/{inviteUserUuid}'; static const String changeUserStatus = '/invite-user/{invitedUserUuid}/disable'; - - // static const String updateAutomation = '/automation/{automationId}'; + static const String terms = '/terms'; + static const String policy = '/policy'; + static const String userAgreements = '/user/agreements/web/{userUuid}'; } diff --git a/pubspec.yaml b/pubspec.yaml index 786a39c9..f4108d5c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -54,6 +54,8 @@ dependencies: time_picker_spinner: ^1.0.0 intl_phone_field: ^3.2.0 number_pagination: ^1.1.6 + url_launcher: ^6.2.5 + flutter_html: ^3.0.0-beta.2 dev_dependencies: flutter_test: From ba7db3a5fb57496b8257ce1d568cd51c6a4c9030 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 22 Jan 2025 17:21:35 +0400 Subject: [PATCH 119/175] updated subspace edit flow --- .../bloc/space_management_bloc.dart | 5 +- .../widgets/community_structure_widget.dart | 21 +-- .../bloc/create_space_model_bloc.dart | 172 ++++++------------ .../create_space_template_body_model.dart | 5 + lib/services/space_model_mang_api.dart | 1 + 5 files changed, 70 insertions(+), 134 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 26e444ca..7e9c6ce3 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -408,7 +408,9 @@ class SpaceManagementBloc Future> saveSpacesHierarchically( List spaces, String communityUuid) async { + print("space"); final orderedSpaces = flattenHierarchy(spaces); + print("parent"); final parentsToDelete = orderedSpaces.where((space) => space.status == SpaceStatus.deleted && @@ -420,9 +422,10 @@ class SpaceManagementBloc await _api.deleteSpace(communityUuid, parent.uuid!); } } catch (e) { - rethrow; // Decide whether to stop execution or continue + rethrow; } } + orderedSpaces.removeWhere((space) => parentsToDelete.contains(space)); for (var space in orderedSpaces) { try { diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 05a80780..dc0ad1cd 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -177,7 +177,7 @@ class _CommunityStructureAreaState extends State { painter: CurvedLinePainter([connection])), ), for (var entry in spaces.asMap().entries) - if (entry.value.status != SpaceStatus.deleted || + if (entry.value.status != SpaceStatus.deleted && entry.value.status != SpaceStatus.parentDeleted) Positioned( left: entry.value.position.dx, @@ -301,7 +301,6 @@ class _CommunityStructureAreaState extends State { List? tags) { setState(() { // Set the first space in the center or use passed position - Offset centerPosition = position ?? _getCenterPosition(screenSize); SpaceModel newSpace = SpaceModel( @@ -385,10 +384,10 @@ class _CommunityStructureAreaState extends State { void flatten(SpaceModel space) { if (space.status == SpaceStatus.deleted || - space.status == SpaceStatus.parentDeleted) return; - + space.status == SpaceStatus.parentDeleted) { + return; + } result.add(space); - for (var child in space.children) { flatten(child); } @@ -456,21 +455,17 @@ class _CommunityStructureAreaState extends State { } void _onDelete() { - if (widget.selectedCommunity != null && - widget.selectedCommunity?.uuid != null && - widget.selectedSpace == null) { - context.read().add(DeleteCommunityEvent( - communityUuid: widget.selectedCommunity!.uuid, - )); - } if (widget.selectedSpace != null) { setState(() { for (var space in spaces) { - if (space.uuid == widget.selectedSpace?.uuid) { + if (space.internalId == widget.selectedSpace?.internalId) { space.status = SpaceStatus.deleted; _markChildrenAsDeleted(space); } } + for (var space in spaces) { + print("space ${space.name} and ${space.status}"); + } _removeConnectionsForDeletedSpaces(); }); } diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 40db384a..d8b39216 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -3,6 +3,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; @@ -204,142 +205,68 @@ class CreateSpaceModelBloc } List tagUpdates = []; final List subspaceUpdates = []; + final List? prevSubspaces = + prevSpaceModel.subspaceModels; + final List? newSubspaces = + newSpaceModel.subspaceModels; tagUpdates = processTagUpdates(prevSpaceModel.tags, newSpaceModel.tags); - if (prevSpaceModel.subspaceModels != null) { - for (var prevSubspace in prevSpaceModel.subspaceModels!) { - if (newSpaceModel.subspaceModels == null) { - subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.delete, - uuid: prevSubspace.uuid, - )); - continue; + if (prevSubspaces != null || newSubspaces != null) { + if (prevSubspaces != null && newSubspaces != null) { + for (var prevSubspace in prevSubspaces!) { + final existsInNew = newSubspaces! + .any((newTag) => newTag.uuid == prevSubspace.uuid); + if (!existsInNew) { + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.delete, uuid: prevSubspace.uuid)); + } } - - final subspaceExistsInNew = newSpaceModel.subspaceModels! - .any((newSubspace) => newSubspace.uuid == prevSubspace.uuid); - if (!subspaceExistsInNew) { + } else if (prevSubspaces != null && newSubspaces == null) { + for (var prevSubspace in prevSubspaces) { subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.delete, - uuid: prevSubspace.uuid, - )); + action: Action.delete, uuid: prevSubspace.uuid)); } } - } - if (newSpaceModel.subspaceModels != null) { - for (var newSubspaceModel in newSpaceModel.subspaceModels!) { - if (newSubspaceModel.uuid == null || - newSubspaceModel.uuid!.isEmpty) { - final List tagUpdatesInSubspace = []; + if (newSubspaces != null) { + for (var newSubspace in newSubspaces!) { + // Tag without UUID + if ((newSubspace.uuid == null || newSubspace.uuid!.isEmpty)) { + final List tagUpdates = []; - if (newSubspaceModel.tags != null) { - for (var tag in newSubspaceModel.tags!) { - tagUpdatesInSubspace.add(TagModelUpdate( + if (newSubspace.tags != null) { + for (var tag in newSubspace.tags!) { + tagUpdates.add(TagModelUpdate( + action: Action.add, + tag: tag.tag, + productUuid: tag.product?.uuid)); + } + } + subspaceUpdates.add(UpdateSubspaceTemplateModel( action: Action.add, - uuid: tag.uuid, - tag: tag.tag, - productUuid: tag.product?.uuid, - )); - } + subspaceName: newSubspace.subspaceName, + tags: tagUpdates)); } - - subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.add, - subspaceName: newSubspaceModel.subspaceName, - tags: tagUpdatesInSubspace, - )); } } - } - if (newSpaceModel.subspaceModels != null && - prevSpaceModel.subspaceModels != null) { - final prevSubspaceMap = { - for (var subspace in prevSpaceModel.subspaceModels!) - subspace.uuid: subspace - }; + if (prevSubspaces != null && newSubspaces != null) { + final newSubspaceMap = { + for (var subspace in newSubspaces!) subspace.uuid: subspace + }; - for (var newSubspace in newSpaceModel.subspaceModels!) { - if (newSubspace.uuid != null && - prevSubspaceMap.containsKey(newSubspace.uuid)) { - final prevSubspace = prevSubspaceMap[newSubspace.uuid]!; - - // Check if modelName has changed - if (newSubspace.subspaceName != prevSubspace.subspaceName) { + for (var prevSubspace in prevSubspaces!) { + final newSubspace = newSubspaceMap[prevSubspace.uuid]; + if (newSubspace != null) { + final List tagSubspaceUpdates = + processTagUpdates(prevSubspace.tags, newSubspace.tags); subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.update, - uuid: newSubspace.uuid, - subspaceName: newSubspace.subspaceName, - )); - } - - // Compare tags within the subspace - final List tagUpdatesInSubspace = []; - if (prevSubspace.tags != null && newSubspace.tags != null) { - final prevTagMap = { - for (var tag in prevSubspace.tags!) tag.uuid: tag - }; - - // Check for deleted tags - for (var prevTag in prevSubspace.tags!) { - if (!newSubspace.tags! - .any((newTag) => newTag.uuid == prevTag.uuid)) { - tagUpdatesInSubspace.add(TagModelUpdate( - action: Action.delete, - uuid: prevTag.uuid, - )); - } - } - - // Check for added tags, including those without a UUID - for (var newTag in newSubspace.tags!) { - if (newTag.uuid == null || newTag.uuid!.isEmpty) { - // Add new tag without UUID - tagUpdatesInSubspace.add(TagModelUpdate( - action: Action.add, - uuid: null, // or generate a new UUID if required - tag: newTag.tag, - productUuid: newTag.product?.uuid, - )); - } else if (!prevSubspace.tags! - .any((prevTag) => prevTag.uuid == newTag.uuid)) { - // Add new tag with UUID - tagUpdatesInSubspace.add(TagModelUpdate( - action: Action.add, - uuid: newTag.uuid, - tag: newTag.tag, - productUuid: newTag.product?.uuid, - )); - } - } - - // Check for updated tags - for (var prevTag in prevSubspace.tags!) { - final newTag = newSubspace.tags!.cast().firstWhere( - (tag) => tag?.uuid == prevTag.uuid, - orElse: () => null, - ); - if (newTag != null && newTag.tag != prevTag.tag) { - tagUpdatesInSubspace.add(TagModelUpdate( - action: Action.update, - uuid: newTag.uuid, - tag: newTag.tag, - )); - } - } - } - - // Add the subspace with updated tags if necessary - if (tagUpdatesInSubspace.isNotEmpty) { - subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.update, - uuid: newSubspace.uuid, - subspaceName: newSubspace.subspaceName, - tags: tagUpdatesInSubspace, - )); - } + action: Action.update, + uuid: newSubspace.uuid, + subspaceName: newSubspace.subspaceName, + tags: tagSubspaceUpdates)); + } else {} } } } @@ -382,6 +309,11 @@ class CreateSpaceModelBloc .add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid)); } } + } else if (prevTags != null && newTags == null) { + for (var prevTag in prevTags) { + tagUpdates + .add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid)); + } } // Case 2: Tags added diff --git a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart index cb8d0aac..9b61f1b0 100644 --- a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart +++ b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart @@ -47,4 +47,9 @@ class CreateSpaceTemplateBodyModel { 'subspaceModels': subspaceModels, }; } + + @override + String toString() { + return toJson().toString(); + } } diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart index eb896432..625397c7 100644 --- a/lib/services/space_model_mang_api.dart +++ b/lib/services/space_model_mang_api.dart @@ -37,6 +37,7 @@ class SpaceModelManagementApi { Future updateSpaceModel( CreateSpaceTemplateBodyModel spaceModel, String spaceModelUuid) async { + print(spaceModel.toJson().toString()); final response = await HTTPService().put( path: ApiEndpoints.updateSpaceModel .replaceAll('{projectId}', TempConst.projectId).replaceAll('{spaceModelUuid}', spaceModelUuid), From 830725254f62d8ad850d17e99b331eb44c64ec68 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 22 Jan 2025 17:22:58 +0400 Subject: [PATCH 120/175] removed logs --- .../all_spaces/bloc/space_management_bloc.dart | 2 -- .../all_spaces/widgets/community_structure_widget.dart | 4 +--- lib/services/space_model_mang_api.dart | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 7e9c6ce3..ff584f52 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -408,9 +408,7 @@ class SpaceManagementBloc Future> saveSpacesHierarchically( List spaces, String communityUuid) async { - print("space"); final orderedSpaces = flattenHierarchy(spaces); - print("parent"); final parentsToDelete = orderedSpaces.where((space) => space.status == SpaceStatus.deleted && diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index dc0ad1cd..f569d252 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -463,9 +463,7 @@ class _CommunityStructureAreaState extends State { _markChildrenAsDeleted(space); } } - for (var space in spaces) { - print("space ${space.name} and ${space.status}"); - } + _removeConnectionsForDeletedSpaces(); }); } diff --git a/lib/services/space_model_mang_api.dart b/lib/services/space_model_mang_api.dart index 625397c7..eb896432 100644 --- a/lib/services/space_model_mang_api.dart +++ b/lib/services/space_model_mang_api.dart @@ -37,7 +37,6 @@ class SpaceModelManagementApi { Future updateSpaceModel( CreateSpaceTemplateBodyModel spaceModel, String spaceModelUuid) async { - print(spaceModel.toJson().toString()); final response = await HTTPService().put( path: ApiEndpoints.updateSpaceModel .replaceAll('{projectId}', TempConst.projectId).replaceAll('{spaceModelUuid}', spaceModelUuid), From bc4af6a237bbd213ba26e2e7dd1ffa9bb9958459 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 22 Jan 2025 17:24:19 +0300 Subject: [PATCH 121/175] user_agreement --- lib/pages/home/bloc/home_bloc.dart | 6 ++---- linux/flutter/generated_plugin_registrant.cc | 4 ++++ linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 ++ windows/flutter/generated_plugin_registrant.cc | 3 +++ windows/flutter/generated_plugins.cmake | 1 + 6 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index 8c887810..1d4bdf8b 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -51,7 +51,6 @@ class HomeBloc extends Bloc { await const FlutterSecureStorage().read(key: UserModel.userUuidKey); user = await HomeApi().fetchUserInfo(uuid); add(FetchTermEvent()); - add(FetchPolicyEvent()); emit(HomeInitial()); } catch (e) { return; @@ -62,7 +61,7 @@ class HomeBloc extends Bloc { try { emit(LoadingHome()); terms = await HomeApi().fetchTerms(); - emit(TermsAgreement()); + add(FetchPolicyEvent()); } catch (e) { return; } @@ -72,7 +71,6 @@ class HomeBloc extends Bloc { try { emit(LoadingHome()); policy = await HomeApi().fetchPolicy(); - emit(PolicyAgreement()); } catch (e) { return; } @@ -90,7 +88,7 @@ class HomeBloc extends Bloc { return; } } - + // static Future fetchUserInfo() async { // try { // var uuid = diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index d0e7f797..38dd0bc6 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,9 +7,13 @@ #include "generated_plugin_registrant.h" #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index b29e9ba0..65240e99 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_linux + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 37af1fe0..51aae316 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,9 +8,11 @@ import Foundation import flutter_secure_storage_macos import path_provider_foundation import shared_preferences_foundation +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 0c507538..2048c455 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,11 @@ #include "generated_plugin_registrant.h" #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 4fc759c4..de626cc8 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_windows + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST From 65d00c923ae26e805f38ab8e3668dc3106cce9a3 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 23 Jan 2025 00:02:28 +0400 Subject: [PATCH 122/175] updated tag issue for subspace --- lib/common/edit_chip.dart | 39 +++ .../all_spaces/model/base_tag.dart | 26 ++ .../all_spaces/model/tag.dart | 35 +-- .../widgets/community_structure_widget.dart | 3 +- .../widgets/dialogs/create_space_dialog.dart | 228 +++++------------- .../views/assign_tag_models_dialog.dart | 12 +- .../create_subspace/bloc/subspace_bloc.dart | 60 ++++- .../create_subspace/bloc/subspace_state.dart | 5 +- .../views/create_subspace_model_dialog.dart | 113 +++++---- .../spaces_management/helper/tag_helper.dart | 3 +- .../bloc/create_space_model_bloc.dart | 2 +- .../space_model/models/tag_model.dart | 35 +-- .../space_model/view/space_model_page.dart | 4 +- .../widgets/button_content_widget.dart | 30 ++- .../dialog/create_space_model_dialog.dart | 8 +- .../widgets/subspace_model_create_widget.dart | 34 +-- .../widgets/subspace_name_label_widget.dart | 40 +++ .../widgets/tag_chips_display_widget.dart | 66 +++-- .../views/add_device_type_model_widget.dart | 14 +- 19 files changed, 408 insertions(+), 349 deletions(-) create mode 100644 lib/common/edit_chip.dart create mode 100644 lib/pages/spaces_management/all_spaces/model/base_tag.dart create mode 100644 lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart diff --git a/lib/common/edit_chip.dart b/lib/common/edit_chip.dart new file mode 100644 index 00000000..7607834d --- /dev/null +++ b/lib/common/edit_chip.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class EditChip extends StatelessWidget { + final String label; + final VoidCallback onTap; + final Color labelColor; + final Color backgroundColor; + final Color borderColor; + final double borderRadius; + + const EditChip({ + Key? key, + this.label = 'Edit', + required this.onTap, + this.labelColor = ColorsManager.spaceColor, + this.backgroundColor = ColorsManager.whiteColors, + this.borderColor = ColorsManager.spaceColor, + this.borderRadius = 16.0, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Chip( + label: Text( + label, + style: TextStyle(color: labelColor), + ), + backgroundColor: backgroundColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadius), + side: BorderSide(color: borderColor), + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/all_spaces/model/base_tag.dart b/lib/pages/spaces_management/all_spaces/model/base_tag.dart new file mode 100644 index 00000000..57f223f4 --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/model/base_tag.dart @@ -0,0 +1,26 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:uuid/uuid.dart'; + +abstract class BaseTag { + String? uuid; + String? tag; + final ProductModel? product; + String internalId; + String? location; + + BaseTag({ + this.uuid, + required this.tag, + this.product, + String? internalId, + this.location, + }) : internalId = internalId ?? const Uuid().v4(); + + Map toJson(); + BaseTag copyWith({ + String? tag, + ProductModel? product, + String? location, + String? internalId, + }); +} diff --git a/lib/pages/spaces_management/all_spaces/model/tag.dart b/lib/pages/spaces_management/all_spaces/model/tag.dart index 98494f7f..34bd08bb 100644 --- a/lib/pages/spaces_management/all_spaces/model/tag.dart +++ b/lib/pages/spaces_management/all_spaces/model/tag.dart @@ -1,22 +1,23 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; import 'package:uuid/uuid.dart'; -class Tag { - String? uuid; - String? tag; - final ProductModel? product; - String internalId; - String? location; - - Tag( - {this.uuid, - required this.tag, - this.product, - String? internalId, - this.location}) - : internalId = internalId ?? const Uuid().v4(); +class Tag extends BaseTag { + Tag({ + String? uuid, + required String? tag, + ProductModel? product, + String? internalId, + String? location, + }) : super( + uuid: uuid, + tag: tag, + product: product, + internalId: internalId, + location: location, + ); factory Tag.fromJson(Map json) { final String internalId = json['internalId'] ?? const Uuid().v4(); @@ -31,15 +32,19 @@ class Tag { ); } + @override Tag copyWith({ String? tag, ProductModel? product, String? location, + String? internalId, }) { return Tag( + uuid: uuid, tag: tag ?? this.tag, product: product ?? this.product, location: location ?? this.location, + internalId: internalId ?? this.internalId, ); } @@ -60,7 +65,7 @@ extension TagModelExtensions on Tag { ..productUuid = product?.uuid; } - CreateTagBodyModel toCreateTagBodyModel() { + CreateTagBodyModel toCreateTagBodyModel() { return CreateTagBodyModel() ..tag = tag ?? '' ..productUuid = product?.uuid; diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index f569d252..8c6e36f5 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -344,6 +344,7 @@ class _CommunityStructureAreaState extends State { builder: (BuildContext context) { return CreateSpaceDialog( products: widget.products, + spaceModels: widget.spaceModels, name: space.name, icon: space.icon, editSpace: space, @@ -463,7 +464,7 @@ class _CommunityStructureAreaState extends State { _markChildrenAsDeleted(space); } } - + _removeConnectionsForDeletedSpaces(); }); } diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 6f3e9fcb..7844381d 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/common/edit_chip.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/add_device_type/views/add_device_type_widget.dart'; @@ -10,15 +11,23 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_mo import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.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/button_content_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/space_icon_const.dart'; class CreateSpaceDialog extends StatefulWidget { - final Function(String, String, List selectedProducts, - SpaceTemplateModel? spaceModel, List? subspaces, List? tags) onCreateSpace; + final Function( + String, + String, + List selectedProducts, + SpaceTemplateModel? spaceModel, + List? subspaces, + List? tags) onCreateSpace; final List? products; final String? name; final String? icon; @@ -211,42 +220,13 @@ class CreateSpaceDialogState extends State { ), const SizedBox(height: 10), selectedSpaceModel == null - ? DefaultButton( + ? TextButton( onPressed: () { _showLinkSpaceModelDialog(context); }, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.link, - width: screenWidth * - 0.015, // Adjust icon size - height: screenWidth * 0.015, - ), - ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Link a space model', - overflow: TextOverflow - .ellipsis, // Prevent overflow - style: Theme.of(context) - .textTheme - .bodyMedium, - ), - ), - ], - ), + child: const ButtonContentWidget( + svgAssets: Assets.link, + label: 'Link a space model', ), ) : Container( @@ -307,7 +287,7 @@ class CreateSpaceDialogState extends State { ), ), Padding( - padding: EdgeInsets.symmetric(horizontal: 8.0), + padding: EdgeInsets.symmetric(horizontal: 6.0), child: Text( 'OR', style: TextStyle( @@ -326,47 +306,21 @@ class CreateSpaceDialogState extends State { ), const SizedBox(height: 25), subspaces == null - ? DefaultButton( - onPressed: () { + ? TextButton( + style: TextButton.styleFrom( + overlayColor: ColorsManager.transparentColor, + ), + onPressed: () async { _showSubSpaceDialog(context, enteredName, [], false, widget.products, subspaces); }, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.addIcon, - width: screenWidth * - 0.015, // Adjust icon size - height: screenWidth * 0.015, - ), - ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Create sub space', - overflow: TextOverflow - .ellipsis, // Prevent overflow - style: Theme.of(context) - .textTheme - .bodyMedium, - ), - ), - ], - ), + child: const ButtonContentWidget( + icon: Icons.add, + label: 'Create Sub Space', ), ) : SizedBox( - width: screenWidth * 0.35, + width: screenWidth * 0.25, child: Container( padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( @@ -383,49 +337,15 @@ class CreateSpaceDialogState extends State { children: [ if (subspaces != null) ...subspaces!.map( - (subspace) => Chip( - label: Text( - subspace.subspaceName, - style: const TextStyle( - color: ColorsManager - .spaceColor), // Text color - ), - backgroundColor: ColorsManager - .whiteColors, // Chip background color - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - 16), // Rounded chip - side: const BorderSide( - color: ColorsManager - .spaceColor), // Border color - ), - ), - ), - GestureDetector( + (subspace) => SubspaceNameDisplayWidget( + text: subspace.subspaceName, + )), + EditChip( onTap: () async { - _showSubSpaceDialog( - context, - enteredName, - [], - false, - widget.products, - subspaces); + _showSubSpaceDialog(context, enteredName, + [], true, widget.products, subspaces); }, - child: Chip( - label: const Text( - 'Edit', - style: TextStyle( - color: ColorsManager.spaceColor), - ), - backgroundColor: - ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: const BorderSide( - color: ColorsManager.spaceColor), - ), - ), - ), + ) ], ), ), @@ -452,7 +372,7 @@ class CreateSpaceDialogState extends State { runSpacing: 8.0, children: [ // Combine tags from spaceModel and subspaces - ..._groupTags([ + ...TagHelper.groupTags([ ...?tags, ...?subspaces?.expand( (subspace) => subspace.tags ?? []) @@ -484,70 +404,31 @@ class CreateSpaceDialogState extends State { ), ), ), - GestureDetector( - onTap: () async { - _showTagCreateDialog(context, enteredName, - widget.products); - // Edit action - }, - child: Chip( - label: const Text( - 'Edit', - style: TextStyle( - color: ColorsManager.spaceColor), - ), - backgroundColor: - ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: const BorderSide( - color: ColorsManager.spaceColor), - ), - ), - ), + + EditChip(onTap: () async { + _showTagCreateDialog( + context, + enteredName, + widget.products, + ); + // Edit action + }) ], ), ), ) - : DefaultButton( + : TextButton( onPressed: () { _showTagCreateDialog( context, enteredName, widget.products); }, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.addIcon, - width: screenWidth * - 0.015, // Adjust icon size - height: screenWidth * 0.015, - ), - ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Add devices', - overflow: TextOverflow - .ellipsis, // Prevent overflow - style: Theme.of(context) - .textTheme - .bodyMedium, - ), - ), - ], - ), + style: TextButton.styleFrom( + padding: EdgeInsets.zero, ), - ) + child: const ButtonContentWidget( + icon: Icons.add, + label: 'Add Devices', + )) ], ), ), @@ -579,8 +460,13 @@ class CreateSpaceDialogState extends State { ? enteredName : (widget.name ?? ''); if (newName.isNotEmpty) { - widget.onCreateSpace(newName, selectedIcon, - selectedProducts, selectedSpaceModel,subspaces,tags); + widget.onCreateSpace( + newName, + selectedIcon, + selectedProducts, + selectedSpaceModel, + subspaces, + tags); Navigator.of(context).pop(); } } @@ -655,7 +541,7 @@ class CreateSpaceDialogState extends State { builder: (BuildContext context) { return CreateSubSpaceDialog( spaceName: name, - dialogTitle: 'Create Sub-space', + dialogTitle: isEdit ? 'Edit Sub-space' : 'Create Sub-space', spaceTags: spaceTags, isEdit: isEdit, products: products, diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index fc778436..5eef92f8 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -29,6 +29,7 @@ class AssignTagModelsDialog extends StatelessWidget { final String title; final BuildContext? pageContext; final List? otherSpaceModels; + final List? allSpaceModels; const AssignTagModelsDialog( {Key? key, @@ -42,7 +43,8 @@ class AssignTagModelsDialog extends StatelessWidget { required this.title, this.pageContext, this.otherSpaceModels, - this.spaceModel}) + this.spaceModel, + this.allSpaceModels}) : super(key: key); @override @@ -212,8 +214,8 @@ class AssignTagModelsDialog extends StatelessWidget { width: double.infinity, child: DialogDropdown( items: locations, - selectedValue: - tag.location ?? 'Main Space', + selectedValue: tag.location ?? + 'Main Space', onSelected: (value) { context .read< @@ -250,8 +252,7 @@ class AssignTagModelsDialog extends StatelessWidget { label: 'Add New Device', onPressed: () async { for (var tag in state.tags) { - if (tag.location == null || - subspaces == null) { + if (tag.location == null) { continue; } @@ -352,6 +353,7 @@ class AssignTagModelsDialog extends StatelessWidget { builder: (BuildContext dialogContext) { return CreateSpaceModelDialog( products: products, + allSpaceModels: allSpaceModels, allTags: allTags, pageContext: pageContext, otherSpaceModels: otherSpaceModels, diff --git a/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart b/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart index 6a072e4a..5426f8f0 100644 --- a/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart +++ b/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart @@ -6,18 +6,23 @@ import 'subspace_event.dart'; import 'subspace_state.dart'; class SubSpaceBloc extends Bloc { - SubSpaceBloc() : super(SubSpaceState([], [], '')) { + SubSpaceBloc() : super(SubSpaceState([], [], '',{})) { on((event, emit) { - final existingNames = - state.subSpaces.map((e) => e.subspaceName).toSet(); + final existingNames = state.subSpaces.map((e) => e.subspaceName).toSet(); if (existingNames.contains(event.subSpace.subspaceName.toLowerCase())) { + final updatedDuplicates = Set.from(state.duplicates) + ..add(event.subSpace.subspaceName.toLowerCase()); + final updatedSubSpaces = List.from(state.subSpaces) + ..add(event.subSpace); emit(SubSpaceState( - state.subSpaces, + updatedSubSpaces, state.updatedSubSpaceModels, - 'Subspace name already exists.', + '*Duplicated sub-space name', + updatedDuplicates, )); } else { + // Add subspace if no duplicate exists final updatedSubSpaces = List.from(state.subSpaces) ..add(event.subSpace); @@ -25,6 +30,8 @@ class SubSpaceBloc extends Bloc { updatedSubSpaces, state.updatedSubSpaceModels, '', + state.duplicates, +// Clear error message )); } }); @@ -38,6 +45,16 @@ class SubSpaceBloc extends Bloc { state.updatedSubSpaceModels, ); + final nameOccurrences = {}; + for (final subSpace in updatedSubSpaces) { + final lowerName = subSpace.subspaceName.toLowerCase(); + nameOccurrences[lowerName] = (nameOccurrences[lowerName] ?? 0) + 1; + } + + final updatedDuplicates = nameOccurrences.entries + .where((entry) => entry.value > 1) + .map((entry) => entry.key) + .toSet(); if (event.subSpace.uuid?.isNotEmpty ?? false) { updatedSubspaceModels.add(UpdateSubspaceModel( action: Action.delete, @@ -45,13 +62,36 @@ class SubSpaceBloc extends Bloc { )); } - emit(SubSpaceState( - updatedSubSpaces, - updatedSubspaceModels, - '', // Clear error message - )); + emit(SubSpaceState(updatedSubSpaces, updatedSubspaceModels, '', + updatedDuplicates // Clear error message + )); }); // Handle UpdateSubSpace Event + + on((event, emit) { + final updatedSubSpaces = state.subSpaces.map((subSpace) { + if (subSpace.uuid == event.updatedSubSpace.uuid) { + return event.updatedSubSpace; + } + return subSpace; + }).toList(); + + final updatedSubspaceModels = List.from( + state.updatedSubSpaceModels, + ); + + updatedSubspaceModels.add(UpdateSubspaceModel( + action: Action.update, + uuid: event.updatedSubSpace.uuid!, + )); + + emit(SubSpaceState( + updatedSubSpaces, + updatedSubspaceModels, + '', + state.duplicates, + )); + }); } } diff --git a/lib/pages/spaces_management/create_subspace/bloc/subspace_state.dart b/lib/pages/spaces_management/create_subspace/bloc/subspace_state.dart index d1374ea1..9521ff2b 100644 --- a/lib/pages/spaces_management/create_subspace/bloc/subspace_state.dart +++ b/lib/pages/spaces_management/create_subspace/bloc/subspace_state.dart @@ -4,23 +4,26 @@ class SubSpaceState { final List subSpaces; final List updatedSubSpaceModels; final String errorMessage; + final Set duplicates; SubSpaceState( this.subSpaces, this.updatedSubSpaceModels, this.errorMessage, + this.duplicates, ); - SubSpaceState copyWith({ List? subSpaces, List? updatedSubSpaceModels, String? errorMessage, + Set? duplicates, }) { return SubSpaceState( subSpaces ?? this.subSpaces, updatedSubSpaceModels ?? this.updatedSubSpaceModels, errorMessage ?? this.errorMessage, + duplicates ?? this.duplicates, ); } } diff --git a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart index 6fd0b936..19babff9 100644 --- a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart @@ -81,41 +81,64 @@ class CreateSubSpaceDialog extends StatelessWidget { spacing: 8.0, runSpacing: 8.0, children: [ - ...state.subSpaces.map( - (subSpace) => Chip( - label: Text( - subSpace.subspaceName, - style: const TextStyle( - color: ColorsManager.spaceColor), - ), - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: const BorderSide( - color: ColorsManager.transparentColor, - width: 0, - ), - ), - deleteIcon: Container( - width: 24, - height: 24, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: ColorsManager.lightGrayColor, - width: 1.5, + ...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: const TextStyle( + color: ColorsManager.spaceColor, ), ), - child: const Icon( - Icons.close, - size: 16, - color: ColorsManager.lightGrayColor, + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + side: BorderSide( + color: isDuplicate + ? ColorsManager.red + : ColorsManager.transparentColor, + width: 0, + ), ), - ), - onDeleted: () => context - .read() - .add(RemoveSubSpace(subSpace)), - ), + 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(RemoveSubSpace(subSpace)), + ); + }, ), SizedBox( width: 200, @@ -142,27 +165,29 @@ class CreateSubSpaceDialog extends StatelessWidget { color: ColorsManager.blackColor), ), ), - if (state.errorMessage.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - state.errorMessage, - style: const TextStyle( - color: ColorsManager.warningRed, - fontSize: 12, - ), - ), - ), ], ), ), + if (state.errorMessage.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + state.errorMessage, + style: const TextStyle( + color: ColorsManager.warningRed, + fontSize: 12, + ), + ), + ), const SizedBox(height: 16), Row( children: [ Expanded( child: CancelButton( label: 'Cancel', - onPressed: () async {}, + onPressed: () async { + Navigator.of(context).pop(); + }, ), ), const SizedBox(width: 10), diff --git a/lib/pages/spaces_management/helper/tag_helper.dart b/lib/pages/spaces_management/helper/tag_helper.dart index d4a0ea55..c0213622 100644 --- a/lib/pages/spaces_management/helper/tag_helper.dart +++ b/lib/pages/spaces_management/helper/tag_helper.dart @@ -1,3 +1,4 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; @@ -33,7 +34,7 @@ class TagHelper { return initialTags; } - static Map groupTags(List tags) { + static Map groupTags(List tags) { final Map groupedTags = {}; for (var tag in tags) { if (tag.product != null) { diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index d8b39216..5553d8b0 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -301,7 +301,7 @@ class CreateSpaceModelBloc if (newTags != null || prevTags != null) { // Case 1: Tags deleted if (prevTags != null && newTags != null) { - for (var prevTag in prevTags!) { + for (var prevTag in prevTags) { final existsInNew = newTags!.any((newTag) => newTag.uuid == prevTag.uuid); if (!existsInNew) { diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart index 48f89167..c1ab4f40 100644 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -1,22 +1,22 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:uuid/uuid.dart'; -class TagModel { - String? uuid; - String? tag; - final ProductModel? product; - String internalId; - String? location; - - TagModel( - {this.uuid, - required this.tag, - this.product, - String? internalId, - this.location}) - : internalId = internalId ?? const Uuid().v4(); - +class TagModel extends BaseTag { + TagModel({ + String? uuid, + required String? tag, + ProductModel? product, + String? internalId, + String? location, + }) : super( + uuid: uuid, + tag: tag, + product: product, + internalId: internalId, + location: location, + ); factory TagModel.fromJson(Map json) { final String internalId = json['internalId'] ?? const Uuid().v4(); @@ -30,16 +30,19 @@ class TagModel { ); } + @override TagModel copyWith( {String? tag, ProductModel? product, + String? uuid, String? location, - String? internalId}) { + String? internalId}) { return TagModel( tag: tag ?? this.tag, product: product ?? this.product, location: location ?? this.location, internalId: internalId ?? this.internalId, + uuid:uuid?? this.uuid ); } diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index ae623e81..cb7bc0c9 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -63,7 +63,8 @@ class SpaceModelPage extends StatelessWidget { } // Render existing space model final model = spaceModels[index]; - final otherModel = List.from(allSpaceModelNames); + final otherModel = + List.from(allSpaceModelNames); otherModel.remove(model.modelName); return GestureDetector( onTap: () { @@ -76,6 +77,7 @@ class SpaceModelPage extends StatelessWidget { spaceModel: model, otherSpaceModels: otherModel, pageContext: context, + allSpaceModels: spaceModels, ); }, ); diff --git a/lib/pages/spaces_management/space_model/widgets/button_content_widget.dart b/lib/pages/spaces_management/space_model/widgets/button_content_widget.dart index 81ecb674..a3ccad7c 100644 --- a/lib/pages/spaces_management/space_model/widgets/button_content_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/button_content_widget.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class ButtonContentWidget extends StatelessWidget { - final IconData icon; + final IconData? icon; final String label; + final String? svgAssets; - const ButtonContentWidget({ - Key? key, - required this.icon, - required this.label, - }) : super(key: key); + const ButtonContentWidget( + {Key? key, this.icon, required this.label, this.svgAssets}) + : super(key: key); @override Widget build(BuildContext context) { @@ -30,10 +30,20 @@ class ButtonContentWidget extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), child: Row( children: [ - Icon( - icon, - color: ColorsManager.spaceColor, - ), + if (icon != null) + Icon( + icon, + color: ColorsManager.spaceColor, + ), + if (svgAssets != null) + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + svgAssets!, + width: screenWidth * 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), + ), const SizedBox(width: 10), Expanded( child: Text( diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index c1bea0fd..c653d1b3 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -22,6 +22,7 @@ class CreateSpaceModelDialog extends StatelessWidget { final SpaceTemplateModel? spaceModel; final BuildContext? pageContext; final List? otherSpaceModels; + final List? allSpaceModels; const CreateSpaceModelDialog( {Key? key, @@ -29,7 +30,8 @@ class CreateSpaceModelDialog extends StatelessWidget { this.allTags, this.spaceModel, this.pageContext, - this.otherSpaceModels}) + this.otherSpaceModels, + this.allSpaceModels}) : super(key: key); @override @@ -138,6 +140,7 @@ class CreateSpaceModelDialog extends StatelessWidget { spaceNameController: spaceNameController, pageContext: pageContext, otherSpaceModels: otherSpaceModels, + allSpaceModels: allSpaceModels, ), const SizedBox(height: 20), SizedBox( @@ -147,7 +150,8 @@ class CreateSpaceModelDialog extends StatelessWidget { Expanded( child: CancelButton( label: 'Cancel', - onPressed: () => Navigator.of(context).pop(), + onPressed: (){ + Navigator.of(context).pop();}, ), ), const SizedBox(width: 10), diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 0dda53a6..8dc981da 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/common/edit_chip.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class SubspaceModelCreate extends StatelessWidget { @@ -46,39 +48,13 @@ class SubspaceModelCreate extends StatelessWidget { spacing: 8.0, runSpacing: 8.0, children: [ - ...subspaces.map((subspace) => Container( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, vertical: 4.0), - decoration: BoxDecoration( - color: ColorsManager.whiteColors, - borderRadius: BorderRadius.circular(10), - border: Border.all( - color: ColorsManager.transparentColor), - ), - child: Text( - subspace.subspaceName, - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: ColorsManager.spaceColor), - ), + ...subspaces.map((subspace) => SubspaceNameDisplayWidget( + text: subspace.subspaceName, )), - GestureDetector( + EditChip( onTap: () async { await _openDialog(context, 'Edit Sub-space'); }, - child: Chip( - label: const Text( - 'Edit', - style: TextStyle(color: ColorsManager.spaceColor), - ), - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: - const BorderSide(color: ColorsManager.spaceColor), - ), - ), ), ], ), diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart new file mode 100644 index 00000000..fd3c90b6 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; + +class SubspaceNameDisplayWidget extends StatelessWidget { + final String text; + final TextStyle? textStyle; + final Color backgroundColor; + final Color borderColor; + final EdgeInsetsGeometry padding; + final BorderRadiusGeometry borderRadius; + + const SubspaceNameDisplayWidget({ + Key? key, + required this.text, + this.textStyle, + this.backgroundColor = Colors.white, + this.borderColor = Colors.transparent, + this.padding = const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + this.borderRadius = const BorderRadius.all(Radius.circular(10)), + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: padding, + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: borderRadius, + border: Border.all(color: borderColor), + ), + child: Text( + text, + style: textStyle ?? + Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: Colors.black), + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index d4111031..76c65805 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/common/edit_chip.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; @@ -18,6 +19,7 @@ class TagChipDisplay extends StatelessWidget { final TextEditingController spaceNameController; final BuildContext? pageContext; final List? otherSpaceModels; + final List? allSpaceModels; const TagChipDisplay(BuildContext context, {Key? key, @@ -28,7 +30,8 @@ class TagChipDisplay extends StatelessWidget { required this.allTags, required this.spaceNameController, this.pageContext, - this.otherSpaceModels}) + this.otherSpaceModels, + this.allSpaceModels}) : super(key: key); @override @@ -83,45 +86,31 @@ class TagChipDisplay extends StatelessWidget { ), ), ), - GestureDetector( - onTap: () async { - // Use the Navigator's context for showDialog - final navigatorContext = - Navigator.of(context).overlay?.context; + EditChip(onTap: () async { + // Use the Navigator's context for showDialog + Navigator.of(context).pop(); - if (navigatorContext != null) { - await showDialog( - barrierDismissible: false, - context: navigatorContext, - builder: (context) => AssignTagModelsDialog( - products: products, + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AssignTagModelsDialog( + products: products, + allSpaceModels: allSpaceModels, + subspaces: subspaces, + pageContext: pageContext, + allTags: allTags, + spaceModel: spaceModel, + otherSpaceModels: otherSpaceModels, + initialTags: TagHelper.generateInitialTags( subspaces: subspaces, - pageContext: pageContext, - allTags: allTags, - spaceModel: spaceModel, - initialTags: TagHelper.generateInitialTags( - subspaces: subspaces, - spaceTagModels: spaceModel?.tags ?? []), - title: 'Edit Device', - addedProducts: - TagHelper.createInitialSelectedProducts( - spaceModel?.tags ?? [], subspaces), - spaceName: spaceModel?.modelName ?? '', - )); - } - }, - child: Chip( - label: const Text( - 'Edit', - style: TextStyle(color: ColorsManager.spaceColor), - ), - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: const BorderSide(color: ColorsManager.spaceColor), - ), - ), - ), + spaceTagModels: spaceModel?.tags ?? []), + title: 'Edit Device', + addedProducts: + TagHelper.createInitialSelectedProducts( + spaceModel?.tags ?? [], subspaces), + spaceName: spaceModel?.modelName ?? '', + )); + }) ], ), ), @@ -141,6 +130,7 @@ class TagChipDisplay extends StatelessWidget { pageContext: pageContext, isCreate: true, spaceModel: spaceModel, + otherSpaceModels: otherSpaceModels, ), ); }, diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index a9d40147..1ef3da9b 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -26,6 +26,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final List? otherSpaceModels; final BuildContext? pageContext; final SpaceTemplateModel? spaceModel; + final List? allSpaceModels; const AddDeviceTypeModelWidget( {super.key, @@ -38,7 +39,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { required this.isCreate, this.pageContext, this.otherSpaceModels, - this.spaceModel}); + this.spaceModel, + this.allSpaceModels}); @override Widget build(BuildContext context) { @@ -106,6 +108,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { context: context, builder: (BuildContext dialogContext) { return CreateSpaceModelDialog( + allSpaceModels: allSpaceModels, products: products, allTags: allTags, pageContext: pageContext, @@ -175,11 +178,12 @@ class AddDeviceTypeModelWidget extends StatelessWidget { context: context, builder: (context) => AssignTagModelsDialog( products: products, + allSpaceModels: allSpaceModels, subspaces: subspaces, addedProducts: state.selectedProducts, allTags: allTags, spaceName: spaceName, - initialTags: state.initialTag, + initialTags: initialTags, otherSpaceModels: otherSpaceModels, title: dialogTitle, spaceModel: spaceModel, @@ -216,13 +220,15 @@ class AddDeviceTypeModelWidget extends StatelessWidget { if (subspace.tags != null) { initialTags.addAll( subspace.tags!.map( - (tag) => tag.copyWith(location: subspace.subspaceName), + (tag) => tag.copyWith( + location: subspace.subspaceName, + tag: tag.tag, + internalId: tag.internalId), ), ); } } } - return initialTags; } } From 2fb6f30ccba1064b0d2d64b388ee9bce676b43cd Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Thu, 23 Jan 2025 01:21:13 +0300 Subject: [PATCH 123/175] Updated pubsepc file --- pubspec.lock | 138 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 117 insertions(+), 21 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index d1c39c65..7833f1ec 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" crypto: dependency: transitive description: @@ -65,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f" + url: "https://pub.dev" + source: hosted + version: "0.17.3" cupertino_icons: dependency: "direct main" description: @@ -182,6 +190,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.0" + flutter_html: + dependency: "direct main" + description: + name: flutter_html + sha256: "02ad69e813ecfc0728a455e4bf892b9379983e050722b1dce00192ee2e41d1ee" + url: "https://pub.dev" + source: hosted + version: "3.0.0-beta.2" flutter_lints: dependency: "direct dev" description: @@ -280,6 +296,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + html: + dependency: transitive + description: + name: html + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + url: "https://pub.dev" + source: hosted + version: "0.15.4" http: dependency: transitive description: @@ -324,18 +348,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -352,6 +376,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + list_counter: + dependency: transitive + description: + name: list_counter + sha256: c447ae3dfcd1c55f0152867090e67e219d42fe6d4f2807db4bbe8b8d69912237 + url: "https://pub.dev" + source: hosted + version: "1.0.2" logging: dependency: transitive description: @@ -372,18 +404,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" nested: dependency: transitive description: @@ -556,7 +588,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: @@ -577,10 +609,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" stream_channel: dependency: transitive description: @@ -593,10 +625,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" term_glyph: dependency: transitive description: @@ -609,10 +641,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" time_picker_spinner: dependency: "direct main" description: @@ -629,6 +661,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" + url: "https://pub.dev" + source: hosted + version: "6.3.14" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" uuid: dependency: "direct main" description: @@ -673,10 +769,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.3.0" web: dependency: transitive description: @@ -710,5 +806,5 @@ packages: source: hosted version: "6.5.0" sdks: - dart: ">=3.4.0 <4.0.0" - flutter: ">=3.22.0" + dart: ">=3.6.0 <4.0.0" + flutter: ">=3.27.0" From 24f7ab6af8677f2083719b168b683d94c7fef1ad Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 23 Jan 2025 10:18:20 +0400 Subject: [PATCH 124/175] changed subspace label --- .../space_model/widgets/subspace_name_label_widget.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart index fd3c90b6..a2920b89 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; class SubspaceNameDisplayWidget extends StatelessWidget { final String text; @@ -12,8 +13,8 @@ class SubspaceNameDisplayWidget extends StatelessWidget { Key? key, required this.text, this.textStyle, - this.backgroundColor = Colors.white, - this.borderColor = Colors.transparent, + this.backgroundColor = ColorsManager.whiteColors, + this.borderColor = ColorsManager.transparentColor, this.padding = const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), this.borderRadius = const BorderRadius.all(Radius.circular(10)), }) : super(key: key); @@ -33,7 +34,7 @@ class SubspaceNameDisplayWidget extends StatelessWidget { Theme.of(context) .textTheme .bodySmall - ?.copyWith(color: Colors.black), + ?.copyWith(color: ColorsManager.spaceColor), ), ); } From c5871be99083037a3a803198600adea98c9103fe Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 23 Jan 2025 10:21:31 +0400 Subject: [PATCH 125/175] removed unmatched alignment --- .../assign_tag_models/views/assign_tag_models_dialog.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index fc778436..61dcd0be 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -97,7 +97,6 @@ class AssignTagModelsDialog extends StatelessWidget { .bodyMedium)), DataColumn( numeric: false, - headingRowAlignment: MainAxisAlignment.start, label: Text('Tag', style: Theme.of(context) .textTheme From 7268253e35fd6ee9cb9ef252514a423bda3e9f3d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 23 Jan 2025 11:45:11 +0400 Subject: [PATCH 126/175] fixed button validation --- .../widgets/dialog/create_space_model_dialog.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index c653d1b3..86a3938f 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -157,7 +157,7 @@ class CreateSpaceModelDialog extends StatelessWidget { const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: state.errorMessage == null || + onPressed: state.errorMessage == null && isNameValid ? () { final updatedSpaceTemplate = @@ -236,7 +236,8 @@ class CreateSpaceModelDialog extends StatelessWidget { : null, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, - foregroundColor: isNameValid + foregroundColor: state.errorMessage == null && + isNameValid ? ColorsManager.whiteColors : ColorsManager.whiteColorsWithOpacity, child: const Text('OK'), From 5563197e9d77e4913db1277995e4a142cf30996c Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 23 Jan 2025 15:20:36 +0400 Subject: [PATCH 127/175] add validation --- .../space_model/bloc/create_space_model_bloc.dart | 2 ++ .../space_model/models/subspace_template_model.dart | 2 +- lib/pages/spaces_management/space_model/models/tag_model.dart | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 5553d8b0..3790991e 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -239,6 +239,7 @@ class CreateSpaceModelBloc for (var tag in newSubspace.tags!) { tagUpdates.add(TagModelUpdate( action: Action.add, + uuid: tag.uuid, tag: tag.tag, productUuid: tag.product?.uuid)); } @@ -325,6 +326,7 @@ class CreateSpaceModelBloc tagUpdates.add(TagModelUpdate( action: Action.add, tag: newTag.tag, + uuid: newTag.uuid, productUuid: newTag.product?.uuid)); processedTags.add(newTag.tag); } diff --git a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart index 6c73741b..9c69b4c8 100644 --- a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart @@ -20,7 +20,7 @@ class SubspaceTemplateModel { final String internalId = json['internalId'] ?? const Uuid().v4(); return SubspaceTemplateModel( - uuid: json['uuid'] ?? '', + uuid: json['uuid'], subspaceName: json['subspaceName'] ?? '', internalId: internalId, disabled: json['disabled'] ?? false, diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart index c1ab4f40..4878ead7 100644 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -21,7 +21,7 @@ class TagModel extends BaseTag { final String internalId = json['internalId'] ?? const Uuid().v4(); return TagModel( - uuid: json['uuid'] ?? '', + uuid: json['uuid'] , internalId: internalId, tag: json['tag'] ?? '', product: json['product'] != null From dac045146e9885cda74be2d5b017d261205b4be4 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 23 Jan 2025 17:02:32 +0400 Subject: [PATCH 128/175] duplicate name validation --- .../bloc/create_space_model_bloc.dart | 13 +++++++++- .../bloc/create_space_model_event.dart | 3 ++- .../dialog/create_space_model_dialog.dart | 25 ++++++++++++------- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 3790991e..1d5ac2a1 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -68,8 +68,12 @@ class CreateSpaceModelBloc on((event, emit) { _space = event.spaceTemplate; - emit(CreateSpaceModelLoaded(_space!)); + final String? errorMessage = _checkDuplicateModelName( + event.allModels ?? [], event.spaceTemplate.modelName); + + emit(CreateSpaceModelLoaded(_space!,errorMessage: errorMessage ?? '')); }); + on((event, emit) { final currentState = state; @@ -352,4 +356,11 @@ class CreateSpaceModelBloc return tagUpdates; } + + String? _checkDuplicateModelName(List allModels, String name) { + if (allModels.contains(name)) { + return "Duplicate Model name"; + } + return null; + } } diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart index 22828941..d0cd245c 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -14,8 +14,9 @@ class LoadSpaceTemplate extends CreateSpaceModelEvent {} class UpdateSpaceTemplate extends CreateSpaceModelEvent { final SpaceTemplateModel spaceTemplate; + List? allModels; - UpdateSpaceTemplate(this.spaceTemplate); + UpdateSpaceTemplate(this.spaceTemplate,this.allModels); } class CreateSpaceTemplate extends CreateSpaceModelEvent { diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 86a3938f..5eedbfbb 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -52,12 +52,12 @@ class CreateSpaceModelDialog extends StatelessWidget { create: (_) { final bloc = CreateSpaceModelBloc(_spaceModelApi); if (spaceModel != null) { - bloc.add(UpdateSpaceTemplate(spaceModel!)); + bloc.add(UpdateSpaceTemplate(spaceModel!,otherSpaceModels)); } else { bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( modelName: '', subspaceModels: const [], - ))); + ),otherSpaceModels)); } spaceNameController.addListener(() { @@ -127,6 +127,11 @@ class CreateSpaceModelDialog extends StatelessWidget { context .read() .add(AddSubspacesToSpaceTemplate(updatedSubspaces)); + + context.read().add( + UpdateSpaceTemplateName( + name: spaceNameController.text, + allModels: otherSpaceModels ?? [])); }, ), const SizedBox(height: 10), @@ -150,16 +155,18 @@ class CreateSpaceModelDialog extends StatelessWidget { Expanded( child: CancelButton( label: 'Cancel', - onPressed: (){ - Navigator.of(context).pop();}, + onPressed: () { + Navigator.of(context).pop(); + }, ), ), const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: state.errorMessage == null && + onPressed: ( state.errorMessage == null) && isNameValid ? () { + final updatedSpaceTemplate = updatedSpaceModel.copyWith( modelName: @@ -236,10 +243,10 @@ class CreateSpaceModelDialog extends StatelessWidget { : null, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, - foregroundColor: state.errorMessage == null && - isNameValid - ? ColorsManager.whiteColors - : ColorsManager.whiteColorsWithOpacity, + foregroundColor: + state.errorMessage == null && isNameValid + ? ColorsManager.whiteColors + : ColorsManager.whiteColorsWithOpacity, child: const Text('OK'), ), ), From d4ed4efcd861b6d32c4f06633bf5380bb2033e02 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 23 Jan 2025 17:48:11 +0400 Subject: [PATCH 129/175] validation fix --- .../bloc/create_space_model_bloc.dart | 9 +++--- .../create_space_template_body_model.dart | 2 +- .../space_model/models/tag_model.dart | 2 +- .../dialog/create_space_model_dialog.dart | 29 ++++++++----------- 4 files changed, 18 insertions(+), 24 deletions(-) diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 1d5ac2a1..0ba36d3b 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -70,8 +70,7 @@ class CreateSpaceModelBloc _space = event.spaceTemplate; final String? errorMessage = _checkDuplicateModelName( event.allModels ?? [], event.spaceTemplate.modelName); - - emit(CreateSpaceModelLoaded(_space!,errorMessage: errorMessage ?? '')); + emit(CreateSpaceModelLoaded(_space!, errorMessage: errorMessage)); }); on((event, emit) { @@ -136,7 +135,7 @@ class CreateSpaceModelBloc final updatedSpace = currentState.space.copyWith(subspaceModels: updatedSubspaces); - emit(CreateSpaceModelLoaded(updatedSpace)); + emit(CreateSpaceModelLoaded(updatedSpace,errorMessage: currentState.errorMessage)); } else { emit(CreateSpaceModelError("Space template not initialized")); } @@ -243,7 +242,7 @@ class CreateSpaceModelBloc for (var tag in newSubspace.tags!) { tagUpdates.add(TagModelUpdate( action: Action.add, - uuid: tag.uuid, + uuid: tag.uuid == '' ? null : tag.uuid, tag: tag.tag, productUuid: tag.product?.uuid)); } @@ -330,7 +329,7 @@ class CreateSpaceModelBloc tagUpdates.add(TagModelUpdate( action: Action.add, tag: newTag.tag, - uuid: newTag.uuid, + uuid: newTag.uuid == '' ? null : newTag.uuid, productUuid: newTag.product?.uuid)); processedTags.add(newTag.tag); } diff --git a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart index 9b61f1b0..ad0770d5 100644 --- a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart +++ b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart @@ -1,5 +1,5 @@ class TagBodyModel { - late String uuid; + late String? uuid; late String tag; late final String? productUuid; diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart index 4878ead7..20bd50e2 100644 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -58,7 +58,7 @@ class TagModel extends BaseTag { extension TagModelExtensions on TagModel { TagBodyModel toTagBodyModel() { return TagBodyModel() - ..uuid = uuid ?? '' + ..uuid = uuid ..tag = tag ?? '' ..productUuid = product?.uuid; } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 5eedbfbb..a825e868 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -52,12 +52,14 @@ class CreateSpaceModelDialog extends StatelessWidget { create: (_) { final bloc = CreateSpaceModelBloc(_spaceModelApi); if (spaceModel != null) { - bloc.add(UpdateSpaceTemplate(spaceModel!,otherSpaceModels)); + bloc.add(UpdateSpaceTemplate(spaceModel!, otherSpaceModels)); } else { - bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( - modelName: '', - subspaceModels: const [], - ),otherSpaceModels)); + bloc.add(UpdateSpaceTemplate( + SpaceTemplateModel( + modelName: '', + subspaceModels: const [], + ), + otherSpaceModels)); } spaceNameController.addListener(() { @@ -127,11 +129,6 @@ class CreateSpaceModelDialog extends StatelessWidget { context .read() .add(AddSubspacesToSpaceTemplate(updatedSubspaces)); - - context.read().add( - UpdateSpaceTemplateName( - name: spaceNameController.text, - allModels: otherSpaceModels ?? [])); }, ), const SizedBox(height: 10), @@ -163,10 +160,8 @@ class CreateSpaceModelDialog extends StatelessWidget { const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: ( state.errorMessage == null) && - isNameValid + onPressed: (state.errorMessage == null) ? () { - final updatedSpaceTemplate = updatedSpaceModel.copyWith( modelName: @@ -243,10 +238,10 @@ class CreateSpaceModelDialog extends StatelessWidget { : null, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, - foregroundColor: - state.errorMessage == null && isNameValid - ? ColorsManager.whiteColors - : ColorsManager.whiteColorsWithOpacity, + foregroundColor: (state.errorMessage != null && + state.errorMessage != '') + ? ColorsManager.whiteColorsWithOpacity + : ColorsManager.whiteColors, child: const Text('OK'), ), ), From cb71b5156593c41976527e56afc3536e32db4a8f Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 24 Jan 2025 20:41:31 +0400 Subject: [PATCH 130/175] community header --- assets/icons/edit_space.svg | 22 +++++++ lib/pages/common/buttons/default_button.dart | 11 +++- ...munity_structure_header_action_button.dart | 59 +++++++++++++++++ .../community_structure_header_button.dart | 65 +++++++++++++++++++ .../community_structure_header_widget.dart | 64 +----------------- .../all_spaces/widgets/sidebar_widget.dart | 4 +- .../bloc/create_space_model_bloc.dart | 22 +++++-- lib/utils/constants/assets.dart | 1 + 8 files changed, 178 insertions(+), 70 deletions(-) create mode 100644 assets/icons/edit_space.svg create mode 100644 lib/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart create mode 100644 lib/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart diff --git a/assets/icons/edit_space.svg b/assets/icons/edit_space.svg new file mode 100644 index 00000000..417cd5bd --- /dev/null +++ b/assets/icons/edit_space.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/pages/common/buttons/default_button.dart b/lib/pages/common/buttons/default_button.dart index 4aa748b7..ecca6138 100644 --- a/lib/pages/common/buttons/default_button.dart +++ b/lib/pages/common/buttons/default_button.dart @@ -19,12 +19,14 @@ class DefaultButton extends StatelessWidget { this.padding, this.borderColor, this.elevation, + this.borderWidth = 1.0, }); final void Function()? onPressed; final Widget child; final double? height; final bool isSecondary; final double? borderRadius; + final double borderWidth; final bool enabled; final double? padding; final bool isDone; @@ -66,13 +68,16 @@ class DefaultButton extends StatelessWidget { }), shape: WidgetStateProperty.all( RoundedRectangleBorder( - side: BorderSide(color: borderColor ?? Colors.transparent), + side: BorderSide( + color: borderColor ?? Colors.transparent, + width: borderWidth, + ), borderRadius: BorderRadius.circular(borderRadius ?? 20), ), ), fixedSize: height != null - ? WidgetStateProperty.all(Size.fromHeight(height!)) - : null, + ? WidgetStateProperty.all(Size.fromHeight(height!)) + : null, padding: WidgetStateProperty.all( EdgeInsets.all(padding ?? 10), ), diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart new file mode 100644 index 00000000..66bfe943 --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class CommunityStructureHeaderActionButtons extends StatelessWidget { + const CommunityStructureHeaderActionButtons({ + super.key, + required this.theme, + required this.isSave, + required this.onSave, + required this.onDelete, + required this.selectedSpace, + }); + + final ThemeData theme; + final bool isSave; + final VoidCallback onSave; + final VoidCallback onDelete; + final SpaceModel? selectedSpace; + + @override + Widget build(BuildContext context) { + final canShowActions = selectedSpace != null && + selectedSpace?.status != SpaceStatus.deleted && + selectedSpace?.status != SpaceStatus.parentDeleted; + + return Wrap( + alignment: WrapAlignment.end, + spacing: 10, + children: [ + if (isSave) + CommunityStructureHeaderButton( + label: "Save", + icon: const Icon(Icons.save, + size: 18, color: ColorsManager.spaceColor), + onPressed: onSave, + theme: theme, + ), + if (canShowActions) ...[ + CommunityStructureHeaderButton( + label: "Edit", + svgAsset: Assets.editSpace, + onPressed: () => {}, + theme: theme, + ), + CommunityStructureHeaderButton( + label: "Delete", + icon: const Icon(Icons.delete, + size: 18, color: ColorsManager.warningRed), + onPressed: onDelete, + theme: theme, + ), + ], + ], + ); + } +} diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart new file mode 100644 index 00000000..4388c965 --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class CommunityStructureHeaderButton extends StatelessWidget { + const CommunityStructureHeaderButton({ + super.key, + required this.label, + this.icon, + required this.onPressed, + this.svgAsset, + required this.theme, + }); + + final String label; + final Widget? icon; + final VoidCallback onPressed; + final String? svgAsset; + final ThemeData theme; + + @override + Widget build(BuildContext context) { + const double buttonHeight = 40; + return ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 100, + minHeight: buttonHeight, + ), + child: DefaultButton( + onPressed: onPressed, + borderWidth: 3, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: ColorsManager.blackColor, + borderRadius: 12.0, + padding: 2.0, + height: buttonHeight, + elevation: 0, + borderColor: ColorsManager.lightGrayColor, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (icon != null) icon!, + if (svgAsset != null) + SvgPicture.asset( + svgAsset!, + width: 30, + height: 30, + ), + const SizedBox(width: 10), + Flexible( + child: Text( + label, + style: theme.textTheme.bodySmall + ?.copyWith(color: ColorsManager.blackColor, fontSize: 14), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart index 02d3819a..18f7b340 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/pages/common/buttons/default_button.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/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart'; import 'package:syncrow_web/pages/spaces_management/create_community/view/create_community_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -141,70 +142,11 @@ class _CommunityStructureHeaderState extends State { ), ), const SizedBox(width: 8), - _buildActionButtons(theme), + CommunityStructureHeaderActionButtons(theme), ], ), ], ); } - Widget _buildActionButtons(ThemeData theme) { - return Wrap( - alignment: WrapAlignment.end, - spacing: 10, - children: [ - if (widget.isSave) - _buildButton( - label: "Save", - icon: const Icon(Icons.save, - size: 18, color: ColorsManager.spaceColor), - onPressed: widget.onSave, - theme: theme), - if(widget.selectedSpace!= null) - _buildButton( - label: "Delete", - icon: const Icon(Icons.delete, - size: 18, color: ColorsManager.warningRed), - onPressed: widget.onDelete, - theme: theme), - ], - ); - } - - Widget _buildButton( - {required String label, - required Widget icon, - required VoidCallback onPressed, - required ThemeData theme}) { - const double buttonHeight = 30; - return ConstrainedBox( - constraints: BoxConstraints(maxWidth: 80, minHeight: buttonHeight), - child: DefaultButton( - onPressed: onPressed, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: ColorsManager.blackColor, - borderRadius: 8.0, - padding: 2.0, - height: buttonHeight, - elevation: 0, - borderColor: ColorsManager.lightGrayColor, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - icon, - const SizedBox(width: 5), - Flexible( - child: Text( - label, - style: theme.textTheme.bodySmall - ?.copyWith(color: ColorsManager.blackColor), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - ), - ], - ), - ), - ); - } } 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 da67e6ed..7dc221a7 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -199,9 +199,11 @@ class _SidebarWidgetState extends State { }, children: hasChildren ? community.spaces + .where((space) => (space.status != SpaceStatus.deleted || + space.status != SpaceStatus.parentDeleted)) .map((space) => _buildSpaceTile(space, community)) .toList() - : null, // Render spaces within the community + : null, ); } diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 0ba36d3b..740ff832 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -135,7 +135,8 @@ class CreateSpaceModelBloc final updatedSpace = currentState.space.copyWith(subspaceModels: updatedSubspaces); - emit(CreateSpaceModelLoaded(updatedSpace,errorMessage: currentState.errorMessage)); + emit(CreateSpaceModelLoaded(updatedSpace, + errorMessage: currentState.errorMessage)); } else { emit(CreateSpaceModelError("Space template not initialized")); } @@ -217,8 +218,8 @@ class CreateSpaceModelBloc if (prevSubspaces != null || newSubspaces != null) { if (prevSubspaces != null && newSubspaces != null) { - for (var prevSubspace in prevSubspaces!) { - final existsInNew = newSubspaces! + for (var prevSubspace in prevSubspaces) { + final existsInNew = newSubspaces .any((newTag) => newTag.uuid == prevSubspace.uuid); if (!existsInNew) { subspaceUpdates.add(UpdateSubspaceTemplateModel( @@ -260,9 +261,20 @@ class CreateSpaceModelBloc for (var subspace in newSubspaces!) subspace.uuid: subspace }; - for (var prevSubspace in prevSubspaces!) { + for (var prevSubspace in prevSubspaces) { final newSubspace = newSubspaceMap[prevSubspace.uuid]; + if (newSubspace != null) { + if(prevSubspace.tags!=null){ + for(var t in prevSubspace.tags!){ + print("old tags are ${t.tag} ${t.uuid}"); + }} + + if(newSubspace.tags!=null){ + for(var t in newSubspace.tags!){ + print("new tags are ${t.tag} ${t.uuid}"); + }} + final List tagSubspaceUpdates = processTagUpdates(prevSubspace.tags, newSubspace.tags); subspaceUpdates.add(UpdateSubspaceTemplateModel( @@ -270,7 +282,7 @@ class CreateSpaceModelBloc uuid: newSubspace.uuid, subspaceName: newSubspace.subspaceName, tags: tagSubspaceUpdates)); - } else {} + } } } } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index a9deb3c7..b151890a 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -259,6 +259,7 @@ class Assets { static const String delete = 'assets/icons/delete.svg'; static const String edit = 'assets/icons/edit.svg'; + static const String editSpace = 'assets/icons/edit_space.svg'; //assets/icons/routine/tab_to_run.svg static const String tabToRun = 'assets/icons/routine/tab_to_run.svg'; From 4258ccdfbd31add6dae31668e04f459c3bb692df Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 24 Jan 2025 20:43:45 +0400 Subject: [PATCH 131/175] fix header issue --- .../widgets/community_structure_header_widget.dart | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart index 18f7b340..0419dc84 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart @@ -3,7 +3,6 @@ import 'package:flutter_svg/flutter_svg.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/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart'; import 'package:syncrow_web/pages/spaces_management/create_community/view/create_community_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -142,11 +141,16 @@ class _CommunityStructureHeaderState extends State { ), ), const SizedBox(width: 8), - CommunityStructureHeaderActionButtons(theme), + CommunityStructureHeaderActionButtons( + theme: theme, + isSave: widget.isSave, + onSave: widget.onSave, + onDelete: widget.onDelete, + selectedSpace: widget.selectedSpace, + ), ], ), ], ); } - } From 9167c8da290cce929579044d5c40116c0e4a1b2e Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sat, 25 Jan 2025 01:29:21 +0400 Subject: [PATCH 132/175] fixed space model updates --- .../views/assign_tag_models_dialog.dart | 184 ++++++++++-------- .../spaces_management/helper/tag_helper.dart | 18 +- .../widgets/tag_chips_display_widget.dart | 2 +- .../views/add_device_type_model_widget.dart | 33 +--- 4 files changed, 121 insertions(+), 116 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 5eef92f8..8cff1b35 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -251,32 +251,15 @@ class AssignTagModelsDialog extends StatelessWidget { builder: (buttonContext) => CancelButton( label: 'Add New Device', onPressed: () async { - for (var tag in state.tags) { - if (tag.location == null) { - continue; - } + final updatedTags = + List.from(state.tags); + final result = + processTags(updatedTags, subspaces); - final previousTagSubspace = - checkTagExistInSubspace( - tag, subspaces ?? []); + final processedTags = + result['updatedTags'] as List; + final processedSubspaces = result['subspaces']; - if (tag.location == 'Main Space') { - removeTagFromSubspace( - tag, previousTagSubspace); - } else if (tag.location != - previousTagSubspace?.subspaceName) { - removeTagFromSubspace( - tag, previousTagSubspace); - moveToNewSubspace(tag, subspaces ?? []); - state.tags.removeWhere( - (t) => t.internalId == tag.internalId); - } else { - updateTagInSubspace( - tag, previousTagSubspace); - state.tags.removeWhere( - (t) => t.internalId == tag.internalId); - } - } if (context.mounted) { Navigator.of(context).pop(); @@ -293,15 +276,16 @@ class AssignTagModelsDialog extends StatelessWidget { allTags: allTags, spaceName: spaceName, otherSpaceModels: otherSpaceModels, - spaceTagModels: state.tags, + spaceTagModels: processedTags, pageContext: pageContext, spaceModel: SpaceTemplateModel( modelName: spaceName, - tags: state.tags, + tags: updatedTags, uuid: spaceModel?.uuid, internalId: spaceModel?.internalId, - subspaceModels: subspaces)), + subspaceModels: + processedSubspaces)), ); } }, @@ -318,33 +302,17 @@ class AssignTagModelsDialog extends StatelessWidget { foregroundColor: ColorsManager.whiteColors, onPressed: state.isSaveEnabled ? () async { - for (var tag in state.tags) { - if (tag.location == null || - subspaces == null) { - continue; - } + final updatedTags = + List.from(state.tags); + final result = + processTags(updatedTags, subspaces); - final previousTagSubspace = - checkTagExistInSubspace( - tag, subspaces ?? []); + final processedTags = + result['updatedTags'] as List; + final processedSubspaces = + result['subspaces'] + as List; - if (tag.location == 'Main Space') { - removeTagFromSubspace( - tag, previousTagSubspace); - } else if (tag.location != - previousTagSubspace?.subspaceName) { - removeTagFromSubspace( - tag, previousTagSubspace); - moveToNewSubspace(tag, subspaces ?? []); - state.tags.removeWhere((t) => - t.internalId == tag.internalId); - } else { - updateTagInSubspace( - tag, previousTagSubspace); - state.tags.removeWhere((t) => - t.internalId == tag.internalId); - } - } Navigator.of(context) .popUntil((route) => route.isFirst); @@ -359,11 +327,12 @@ class AssignTagModelsDialog extends StatelessWidget { otherSpaceModels: otherSpaceModels, spaceModel: SpaceTemplateModel( modelName: spaceName, - tags: state.tags, + tags: processedTags, uuid: spaceModel?.uuid, internalId: spaceModel?.internalId, - subspaceModels: subspaces), + subspaceModels: + processedSubspaces), ); }, ); @@ -397,39 +366,100 @@ class AssignTagModelsDialog extends StatelessWidget { .toList(); } - void removeTagFromSubspace(TagModel tag, SubspaceTemplateModel? subspace) { - subspace?.tags?.removeWhere((t) => t.internalId == tag.internalId); - } - - SubspaceTemplateModel? checkTagExistInSubspace( + int? checkTagExistInSubspace( TagModel tag, List? subspaces) { if (subspaces == null) return null; - for (var subspace in subspaces) { - if (subspace.tags == null) return null; + for (int i = 0; i < subspaces.length; i++) { + final subspace = subspaces[i]; + if (subspace.tags == null) continue; for (var t in subspace.tags!) { - if (tag.internalId == t.internalId) return subspace; + if (tag.internalId == t.internalId) { + return i; + } } } return null; } - void moveToNewSubspace(TagModel tag, List subspaces) { - final targetSubspace = subspaces - .firstWhere((subspace) => subspace.subspaceName == tag.location); + Map processTags( + List updatedTags, List? subspaces) { + final modifiedTags = List.from(updatedTags); + final modifiedSubspaces = List.from(subspaces ?? []); - targetSubspace.tags ??= []; - if (targetSubspace.tags?.any((t) => t.internalId == tag.internalId) != - true) { - targetSubspace.tags?.add(tag); - } - } + for (var tag in modifiedTags.toList()) { + if (modifiedSubspaces.isEmpty) continue; - void updateTagInSubspace(TagModel tag, SubspaceTemplateModel? subspace) { - final currentTag = subspace?.tags?.firstWhere( - (t) => t.internalId == tag.internalId, - ); - if (currentTag != null) { - currentTag.tag = tag.tag; + final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces); + + if ((tag.location == 'Main Space' || tag.location == null) && + (prevIndice == null || + modifiedSubspaces[prevIndice].subspaceName == 'Main Space')) { + continue; + } + + if ((tag.location == 'Main Space' || tag.location == null) && + prevIndice != null) { + modifiedSubspaces[prevIndice] + .tags + ?.removeWhere((t) => t.internalId == tag.internalId); + continue; + } + + if ((tag.location != 'Main Space' && tag.location != null) && + prevIndice == null) { + final newIndex = modifiedSubspaces + .indexWhere((subspace) => subspace.subspaceName == tag.location); + if (newIndex != -1) { + if (modifiedSubspaces[newIndex] + .tags + ?.any((t) => t.internalId == tag.internalId) != + true) { + tag.location = modifiedSubspaces[newIndex].subspaceName; + modifiedSubspaces[newIndex].tags?.add(tag); + } + } + modifiedTags.removeWhere((t) => t.internalId == tag.internalId); + continue; + } + + if ((tag.location != 'Main Space' && tag.location != null) && + tag.location != modifiedSubspaces[prevIndice!].subspaceName) { + modifiedSubspaces[prevIndice] + .tags + ?.removeWhere((t) => t.internalId == tag.internalId); + final newIndex = modifiedSubspaces + .indexWhere((subspace) => subspace.subspaceName == tag.location); + if (newIndex != -1) { + if (modifiedSubspaces[newIndex] + .tags + ?.any((t) => t.internalId == tag.internalId) != + true) { + tag.location = modifiedSubspaces[newIndex].subspaceName; + modifiedSubspaces[newIndex].tags?.add(tag); + } + } + + modifiedTags.removeWhere((t) => t.internalId == tag.internalId); + continue; + } + + if ((tag.location != 'Main Space' && tag.location != null) && + tag.location == modifiedSubspaces[prevIndice!].subspaceName) { + modifiedTags.removeWhere((t) => t.internalId == tag.internalId); + continue; + } + + if ((tag.location == 'Main Space' || tag.location == null) && + prevIndice != null) { + modifiedSubspaces[prevIndice] + .tags + ?.removeWhere((t) => t.internalId == tag.internalId); + } } + + return { + 'updatedTags': modifiedTags, + 'subspaces': modifiedSubspaces, + }; } } diff --git a/lib/pages/spaces_management/helper/tag_helper.dart b/lib/pages/spaces_management/helper/tag_helper.dart index c0213622..4fa86b88 100644 --- a/lib/pages/spaces_management/helper/tag_helper.dart +++ b/lib/pages/spaces_management/helper/tag_helper.dart @@ -18,19 +18,19 @@ class TagHelper { if (subspaces != null) { for (var subspace in subspaces) { if (subspace.tags != null) { - for (var existingTag in subspace.tags!) { - initialTags.addAll( - subspace.tags!.map( - (tag) => tag.copyWith( - location: subspace.subspaceName, - internalId: existingTag.internalId, - tag: existingTag.tag), + initialTags.addAll( + subspace.tags!.map( + (tag) => tag.copyWith( + location: subspace.subspaceName, + internalId: tag.internalId, + tag: tag.tag, ), - ); - } + ), + ); } } } + return initialTags; } diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 76c65805..11c7ca5b 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -89,7 +89,7 @@ class TagChipDisplay extends StatelessWidget { EditChip(onTap: () async { // Use the Navigator's context for showDialog Navigator.of(context).pop(); - + await showDialog( barrierDismissible: false, context: context, diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index 1ef3da9b..9d0eac96 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; @@ -123,7 +124,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { }, ); } else { - final initialTags = generateInitialTags( + final initialTags = TagHelper.generateInitialTags( spaceTagModels: spaceTagModels, subspaces: subspaces, ); @@ -165,7 +166,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { : () async { if (state is AddDeviceModelLoaded && state.selectedProducts.isNotEmpty) { - final initialTags = generateInitialTags( + final initialTags = + TagHelper.generateInitialTags( spaceTagModels: spaceTagModels, subspaces: subspaces, ); @@ -204,31 +206,4 @@ class AddDeviceTypeModelWidget extends StatelessWidget { ), ); } - - List generateInitialTags({ - List? spaceTagModels, - List? subspaces, - }) { - final List initialTags = []; - - if (spaceTagModels != null) { - initialTags.addAll(spaceTagModels); - } - - if (subspaces != null) { - for (var subspace in subspaces) { - if (subspace.tags != null) { - initialTags.addAll( - subspace.tags!.map( - (tag) => tag.copyWith( - location: subspace.subspaceName, - tag: tag.tag, - internalId: tag.internalId), - ), - ); - } - } - } - return initialTags; - } } From 4907eebc42a46199b70e721b58b8b52aefbc0f8d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 27 Jan 2025 00:33:50 +0400 Subject: [PATCH 133/175] added duplicate --- assets/icons/duplicate.svg | 16 + assets/icons/space_delete.svg | 9 + .../bloc/add_device_model_bloc.dart | 86 +++-- .../bloc/add_device_state.dart | 36 ++ .../bloc/add_device_type_model_event.dart | 24 +- .../views/add_device_type_widget.dart | 141 ++++--- .../widgets/scrollable_grid_view_widget.dart | 8 +- ...munity_structure_header_action_button.dart | 31 +- .../community_structure_header_button.dart | 8 +- .../community_structure_header_widget.dart | 9 +- .../widgets/community_structure_widget.dart | 177 ++++++++- .../widgets/dialogs/create_space_dialog.dart | 136 +++---- .../dialogs/duplicate_process_dialog.dart | 86 +++++ .../assign_tag/bloc/assign_tag_bloc.dart | 12 +- .../assign_tag/views/assign_tag_dialog.dart | 352 ++++++++++-------- .../views/assign_tag_models_dialog.dart | 2 +- .../create_subspace/bloc/subspace_bloc.dart | 48 ++- .../bloc/subspace_model_bloc.dart | 54 ++- .../spaces_management/helper/tag_helper.dart | 68 +++- .../bloc/create_space_model_bloc.dart | 173 +++++---- lib/utils/color_manager.dart | 2 +- lib/utils/constants/assets.dart | 2 + 22 files changed, 1025 insertions(+), 455 deletions(-) create mode 100644 assets/icons/duplicate.svg create mode 100644 assets/icons/space_delete.svg create mode 100644 lib/pages/spaces_management/add_device_type/bloc/add_device_state.dart create mode 100644 lib/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.dart diff --git a/assets/icons/duplicate.svg b/assets/icons/duplicate.svg new file mode 100644 index 00000000..1faa1bab --- /dev/null +++ b/assets/icons/duplicate.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/space_delete.svg b/assets/icons/space_delete.svg new file mode 100644 index 00000000..90c3413e --- /dev/null +++ b/assets/icons/space_delete.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lib/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart b/lib/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart index 10f3327e..e84851c5 100644 --- a/lib/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart +++ b/lib/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart @@ -1,38 +1,74 @@ import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_state.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart'; -class AddDeviceTypeBloc - extends Bloc> { - AddDeviceTypeBloc(List initialProducts) - : super(initialProducts) { +class AddDeviceTypeBloc extends Bloc { + AddDeviceTypeBloc() : super(AddDeviceInitial()) { + on(_onInitializeTagModels); on(_onUpdateProductCount); } - void _onUpdateProductCount( - UpdateProductCountEvent event, Emitter> emit) { - final existingProduct = state.firstWhere( - (p) => p.productId == event.productId, - orElse: () => SelectedProduct(productId: event.productId, count: 0,productName: event.productName,product: event.product ), - ); + void _onInitializeTagModels( + InitializeDevice event, Emitter emit) { + emit(AddDeviceLoaded( + selectedProducts: event.addedProducts, + initialTag: event.initialTags, + )); + } - if (event.count > 0) { - if (!state.contains(existingProduct)) { - emit([ - ...state, - SelectedProduct(productId: event.productId, count: event.count, productName: event.productName, product: event.product) - ]); + void _onUpdateProductCount( + UpdateProductCountEvent event, Emitter emit) { + final currentState = state; + + if (currentState is AddDeviceLoaded) { + final existingProduct = currentState.selectedProducts.firstWhere( + (p) => p.productId == event.productId, + orElse: () => SelectedProduct( + productId: event.productId, + count: 0, + productName: event.productName, + product: event.product, + ), + ); + + List updatedProducts; + + if (event.count > 0) { + if (!currentState.selectedProducts.contains(existingProduct)) { + updatedProducts = [ + ...currentState.selectedProducts, + SelectedProduct( + productId: event.productId, + count: event.count, + productName: event.productName, + product: event.product, + ), + ]; + } else { + updatedProducts = currentState.selectedProducts.map((p) { + if (p.productId == event.productId) { + return SelectedProduct( + productId: p.productId, + count: event.count, + productName: p.productName, + product: p.product, + ); + } + return p; + }).toList(); + } } else { - final updatedList = state.map((p) { - if (p.productId == event.productId) { - return SelectedProduct(productId: p.productId, count: event.count, productName: p.productName,product: p.product); - } - return p; - }).toList(); - emit(updatedList); + // Remove the product if the count is 0 + updatedProducts = currentState.selectedProducts + .where((p) => p.productId != event.productId) + .toList(); } - } else { - emit(state.where((p) => p.productId != event.productId).toList()); + + // Emit the updated state + emit(AddDeviceLoaded( + selectedProducts: updatedProducts, + initialTag: currentState.initialTag)); } } } diff --git a/lib/pages/spaces_management/add_device_type/bloc/add_device_state.dart b/lib/pages/spaces_management/add_device_type/bloc/add_device_state.dart new file mode 100644 index 00000000..e1fa2593 --- /dev/null +++ b/lib/pages/spaces_management/add_device_type/bloc/add_device_state.dart @@ -0,0 +1,36 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; + +abstract class AddDeviceState extends Equatable { + const AddDeviceState(); + + @override + List get props => []; +} + +class AddDeviceInitial extends AddDeviceState {} + +class AddDeviceLoading extends AddDeviceState {} + +class AddDeviceLoaded extends AddDeviceState { + final List selectedProducts; + final List initialTag; + + const AddDeviceLoaded({ + required this.selectedProducts, + required this.initialTag, + }); + + @override + List get props => [selectedProducts, initialTag]; +} + +class AddDeviceError extends AddDeviceState { + final String errorMessage; + + const AddDeviceError(this.errorMessage); + + @override + List get props => [errorMessage]; +} diff --git a/lib/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart b/lib/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart index addb6d67..254b78fd 100644 --- a/lib/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart +++ b/lib/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart @@ -1,7 +1,11 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; abstract class AddDeviceTypeEvent extends Equatable { + const AddDeviceTypeEvent(); + @override List get props => []; } @@ -12,8 +16,26 @@ class UpdateProductCountEvent extends AddDeviceTypeEvent { final String productName; final ProductModel product; - UpdateProductCountEvent({required this.productId, required this.count, required this.productName, required this.product}); + UpdateProductCountEvent( + {required this.productId, + required this.count, + required this.productName, + required this.product}); @override List get props => [productId, count]; } + + +class InitializeDevice extends AddDeviceTypeEvent { + final List initialTags; + final List addedProducts; + + const InitializeDevice({ + this.initialTags = const [], + required this.addedProducts, + }); + + @override + List get props => [initialTags, addedProducts]; +} diff --git a/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart b/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart index 9b9d6886..b26dbcc7 100644 --- a/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart +++ b/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart @@ -1,17 +1,20 @@ 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/add_device_type/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_state.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; - class AddDeviceTypeWidget extends StatelessWidget { final List? products; final ValueChanged>? onProductsSelected; @@ -20,8 +23,7 @@ class AddDeviceTypeWidget extends StatelessWidget { final List? spaceTags; final List? allTags; final String spaceName; - final Function(List,List?)? onSave; - + final Function(List, List?)? onSave; const AddDeviceTypeWidget( {super.key, @@ -44,30 +46,45 @@ class AddDeviceTypeWidget extends StatelessWidget { : 3; return BlocProvider( - create: (_) => AddDeviceTypeBloc(initialSelectedProducts ?? []), + create: (_) => AddDeviceTypeBloc() + ..add(InitializeDevice( + initialTags: spaceTags ?? [], + addedProducts: initialSelectedProducts ?? [], + )), child: Builder( builder: (context) => AlertDialog( title: const Text('Add Devices'), backgroundColor: ColorsManager.whiteColors, - content: SingleChildScrollView( - child: Container( - width: size.width * 0.9, - height: size.height * 0.65, - color: ColorsManager.textFieldGreyColor, - child: Column( - children: [ - const SizedBox(height: 16), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: ScrollableGridViewWidget( - products: products, crossAxisCount: crossAxisCount), - ), + content: BlocBuilder( + builder: (context, state) { + if (state is AddDeviceLoading) { + return const Center(child: CircularProgressIndicator()); + } + if (state is AddDeviceLoaded) { + return SingleChildScrollView( + child: Container( + width: size.width * 0.9, + height: size.height * 0.65, + color: ColorsManager.textFieldGreyColor, + child: Column( + children: [ + const SizedBox(height: 16), + Expanded( + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 20.0), + child: ScrollableGridViewWidget( + products: products, + crossAxisCount: crossAxisCount), + ), + ), + ], ), - ], - ), - ), - ), + ), + ); + } + return const SizedBox(); + }), actions: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -78,43 +95,53 @@ class AddDeviceTypeWidget extends StatelessWidget { Navigator.of(context).pop(); }, ), - ActionButton( - label: 'Continue', - backgroundColor: ColorsManager.secondaryColor, - foregroundColor: ColorsManager.whiteColors, - onPressed: () async { - final currentState = - context.read().state; - Navigator.of(context).pop(); + SizedBox( + width: 140, + child: BlocBuilder( + builder: (context, state) { + final isDisabled = state is AddDeviceLoaded && + state.selectedProducts.isEmpty; + return DefaultButton( + borderRadius: 10, + backgroundColor: ColorsManager.secondaryColor, + foregroundColor: isDisabled + ? ColorsManager.whiteColorsWithOpacity + : ColorsManager.whiteColors, + onPressed: () async { + if (state is AddDeviceLoaded && + state.selectedProducts.isNotEmpty) { + final initialTags = + TagHelper.generateInitialForTags( + spaceTags: spaceTags, + subspaces: subspaces, + ); + Navigator.of(context).pop(); - if (currentState.isNotEmpty) { - final initialTags = generateInitialTags( - spaceTags: spaceTags, - subspaces: subspaces, - ); - - final dialogTitle = initialTags.isNotEmpty - ? 'Edit Device' - : 'Assign Tags'; - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => AssignTagDialog( - products: products, - subspaces: subspaces, - addedProducts: currentState, - allTags: allTags, - spaceName: spaceName, - initialTags: initialTags, - title: dialogTitle, - onSave: (tags,subspaces){ - onSave!(tags,subspaces); + final dialogTitle = initialTags.isNotEmpty + ? 'Edit Device' + : 'Assign Tags'; + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AssignTagDialog( + products: products, + subspaces: subspaces, + addedProducts: state.selectedProducts, + allTags: allTags, + spaceName: spaceName, + initialTags: initialTags, + title: dialogTitle, + onSave: (tags, subspaces) { + onSave!(tags, subspaces); + }, + ), + ); + } }, - ), - ); - } - }, - ), + child: const Text('Next'), + ); + }, + )), ], ), ], diff --git a/lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart b/lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart index aeee6f1b..97bcf6d1 100644 --- a/lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart +++ b/lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_state.dart'; import 'package:syncrow_web/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; @@ -24,8 +25,11 @@ class ScrollableGridViewWidget extends StatelessWidget { return Scrollbar( controller: scrollController, thumbVisibility: true, - child: BlocBuilder>( - builder: (context, productCounts) { + child: BlocBuilder( + builder: (context, state) { + final productCounts = state is AddDeviceLoaded + ? state.selectedProducts + : []; return GridView.builder( controller: scrollController, shrinkWrap: true, diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart index 66bfe943..f5188c1e 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart @@ -5,19 +5,23 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class CommunityStructureHeaderActionButtons extends StatelessWidget { - const CommunityStructureHeaderActionButtons({ - super.key, - required this.theme, - required this.isSave, - required this.onSave, - required this.onDelete, - required this.selectedSpace, - }); + const CommunityStructureHeaderActionButtons( + {super.key, + required this.theme, + required this.isSave, + required this.onSave, + required this.onDelete, + required this.selectedSpace, + required this.onDuplicate, + required this.onEdit}); final ThemeData theme; final bool isSave; final VoidCallback onSave; final VoidCallback onDelete; + final VoidCallback onDuplicate; + final VoidCallback onEdit; + final SpaceModel? selectedSpace; @override @@ -42,13 +46,18 @@ class CommunityStructureHeaderActionButtons extends StatelessWidget { CommunityStructureHeaderButton( label: "Edit", svgAsset: Assets.editSpace, - onPressed: () => {}, + onPressed: onEdit, + theme: theme, + ), + CommunityStructureHeaderButton( + label: "Duplicate", + svgAsset: Assets.duplicate, + onPressed: onDuplicate, theme: theme, ), CommunityStructureHeaderButton( label: "Delete", - icon: const Icon(Icons.delete, - size: 18, color: ColorsManager.warningRed), + svgAsset: Assets.spaceDelete, onPressed: onDelete, theme: theme, ), diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart index 4388c965..0369f7cd 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart @@ -24,12 +24,12 @@ class CommunityStructureHeaderButton extends StatelessWidget { const double buttonHeight = 40; return ConstrainedBox( constraints: const BoxConstraints( - maxWidth: 100, + maxWidth: 130, minHeight: buttonHeight, ), child: DefaultButton( onPressed: onPressed, - borderWidth: 3, + borderWidth: 2, backgroundColor: ColorsManager.textFieldGreyColor, foregroundColor: ColorsManager.blackColor, borderRadius: 12.0, @@ -44,8 +44,8 @@ class CommunityStructureHeaderButton extends StatelessWidget { if (svgAsset != null) SvgPicture.asset( svgAsset!, - width: 30, - height: 30, + width: 20, + height: 20, ), const SizedBox(width: 10), Flexible( diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart index 0419dc84..6bc35cca 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart @@ -14,6 +14,9 @@ class CommunityStructureHeader extends StatefulWidget { final TextEditingController nameController; final VoidCallback onSave; final VoidCallback onDelete; + final VoidCallback onEdit; + final VoidCallback onDuplicate; + final VoidCallback onEditName; final ValueChanged onNameSubmitted; final List communities; @@ -32,7 +35,9 @@ class CommunityStructureHeader extends StatefulWidget { required this.onNameSubmitted, this.community, required this.communities, - this.selectedSpace}); + this.selectedSpace, + required this.onDuplicate, + required this.onEdit}); @override State createState() => @@ -146,6 +151,8 @@ class _CommunityStructureHeaderState extends State { isSave: widget.isSave, onSave: widget.onSave, onDelete: widget.onDelete, + onDuplicate: widget.onDuplicate, + onEdit: widget.onEdit, selectedSpace: widget.selectedSpace, ), ], diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 8c6e36f5..b5f54708 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -4,6 +4,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; // Syncrow project imports import 'package:syncrow_web/pages/common/buttons/add_space_button.dart'; +import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/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/product_model.dart'; @@ -17,6 +19,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/blank_com import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/curved_line_painter.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_card_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_container_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; @@ -133,6 +136,8 @@ class _CommunityStructureAreaState extends State { onSave: _saveSpaces, selectedSpace: widget.selectedSpace, onDelete: _onDelete, + onDuplicate: () => {_onDuplicate(context)}, + onEdit: () => {}, onEditName: () { setState(() { isEditingName = !isEditingName; @@ -328,7 +333,6 @@ class _CommunityStructureAreaState extends State { parentSpace.addOutgoingConnection(newConnection); parentSpace.children.add(newSpace); } - spaces.add(newSpace); _updateNodePosition(newSpace, newSpace.position); }); @@ -546,4 +550,175 @@ class _CommunityStructureAreaState extends State { space.status == SpaceStatus.modified || space.status == SpaceStatus.deleted); } + + void _onDuplicate(BuildContext parentContext) { + final screenWidth = MediaQuery.of(context).size.width; + + if (widget.selectedSpace != null) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + backgroundColor: ColorsManager.whiteColors, + title: Text( + "Duplicate Space", + textAlign: TextAlign.center, + style: Theme.of(context) + .textTheme + .headlineLarge + ?.copyWith(color: ColorsManager.blackColor), + ), + content: SizedBox( + width: screenWidth * 0.4, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text("Are you sure you want to duplicate?", + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineSmall), + const SizedBox(height: 15), + Text("All the child spaces will be duplicated.", + textAlign: TextAlign.center, + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(color: ColorsManager.lightGrayColor)), + const SizedBox(width: 15), + ], + ), + ), + actions: [ + Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + SizedBox( + width: 200, + child: CancelButton( + onPressed: () { + Navigator.of(context).pop(); + }, + label: "Cancel", + ), + ), + const SizedBox(width: 10), + SizedBox( + width: 200, + child: DefaultButton( + onPressed: () { + Navigator.of(context).pop(); + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return DuplicateProcessDialog( + onDuplicate: () { + _duplicateSpace(widget.selectedSpace!); + _deselectSpace(parentContext); + }, + ); + }, + ); + }, + backgroundColor: ColorsManager.secondaryColor, + borderRadius: 10, + foregroundColor: ColorsManager.whiteColors, + child: const Text('OK'), + ), + ), + ]) + ], + ); + }, + ); + } + } + + void _duplicateSpace(SpaceModel space) { + final Map originalToDuplicate = {}; + const double horizontalGap = 150.0; + const double verticalGap = 100.0; + + SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition) { + // Find a new position for the duplicated space + Offset newPosition = parentPosition + Offset(horizontalGap, 0); + + // Avoid overlapping with existing spaces + while (spaces.any((s) => + (s.position - newPosition).distance < horizontalGap && + s.status != SpaceStatus.deleted)) { + newPosition += Offset(horizontalGap, 0); + } + + // Create the duplicated space + final duplicated = SpaceModel( + name: "${original.name} (Copy)", + icon: original.icon, + position: newPosition, + isPrivate: original.isPrivate, + children: [], + status: SpaceStatus.newSpace, + parent: original.parent, + spaceModel: original.spaceModel, + subspaces: original.subspaces, + tags: original.tags, + ); + + originalToDuplicate[original] = duplicated; + + // Copy the children of the original space to the duplicated space + Offset childStartPosition = newPosition + Offset(0, verticalGap); + for (final child in original.children) { + final duplicatedChild = duplicateRecursive(child, childStartPosition); + duplicated.children.add(duplicatedChild); + duplicatedChild.parent = + duplicated; // Set the parent for the duplicated child + childStartPosition += Offset(0, verticalGap); + } + + return duplicated; + } + + // Duplicate the selected space and its children + final duplicatedSpace = duplicateRecursive(space, space.position); + + // Ensure the duplicated space has the same parent as the original + if (space.parent != null) { + final parentSpace = space.parent!; + final duplicatedParent = originalToDuplicate[parentSpace] ?? parentSpace; + duplicatedSpace.parent = duplicatedParent; + duplicatedParent.children.add(duplicatedSpace); + } + + // Flatten the hierarchy of the duplicated spaces + List flattenHierarchy(SpaceModel root) { + final List result = []; + void traverse(SpaceModel node) { + result.add(node); + for (final child in node.children) { + traverse(child); + } + } + + traverse(root); + return result; + } + + final duplicatedSpacesList = flattenHierarchy(duplicatedSpace); + + setState(() { + spaces.addAll(duplicatedSpacesList); + + // Duplicate the connections + for (final connection in connections) { + if (originalToDuplicate.containsKey(connection.startSpace) && + originalToDuplicate.containsKey(connection.endSpace)) { + connections.add( + Connection( + startSpace: originalToDuplicate[connection.startSpace]!, + endSpace: originalToDuplicate[connection.endSpace]!, + direction: connection.direction, + ), + ); + } + } + }); + } } diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 7844381d..2b8d4aaf 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart'; @@ -409,7 +410,9 @@ class CreateSpaceDialogState extends State { _showTagCreateDialog( context, enteredName, + widget.isEdit, widget.products, + subspaces, ); // Edit action }) @@ -420,7 +423,12 @@ class CreateSpaceDialogState extends State { : TextButton( onPressed: () { _showTagCreateDialog( - context, enteredName, widget.products); + context, + enteredName, + widget.isEdit, + widget.products, + subspaces, + ); }, style: TextButton.styleFrom( padding: EdgeInsets.zero, @@ -558,85 +566,57 @@ class CreateSpaceDialogState extends State { ); } - void _showTagCreateDialog( - BuildContext context, String name, List? products) { - showDialog( - context: context, - builder: (BuildContext context) { - return AddDeviceTypeWidget( - spaceName: name, - products: products, - subspaces: subspaces, - spaceTags: tags, - allTags: [], - initialSelectedProducts: - createInitialSelectedProducts(tags, subspaces), - onSave: (selectedSpaceTags, selectedSubspaces) { - setState(() { - tags = selectedSpaceTags; - selectedSpaceModel = null; + void _showTagCreateDialog(BuildContext context, String name, bool isEdit, + List? products, List? subspaces) { + isEdit + ? showDialog( + context: context, + builder: (BuildContext context) { + return AssignTagDialog( + title: 'Edit Device', + addedProducts: TagHelper.createInitialSelectedProductsForTags( + tags, subspaces), + spaceName: name, + products: products, + subspaces: subspaces, + allTags: [], + onSave: (selectedSpaceTags, selectedSubspaces) {}, + ); + }, + ) + : showDialog( + context: context, + builder: (BuildContext context) { + return AddDeviceTypeWidget( + spaceName: name, + products: products, + subspaces: subspaces, + spaceTags: tags, + allTags: [], + initialSelectedProducts: + TagHelper.createInitialSelectedProductsForTags( + tags, subspaces), + onSave: (selectedSpaceTags, selectedSubspaces) { + setState(() { + tags = selectedSpaceTags; + selectedSpaceModel = null; - if (selectedSubspaces != null) { - if (subspaces != null) { - for (final subspace in subspaces!) { - for (final selectedSubspace in selectedSubspaces) { - if (subspace.subspaceName == - selectedSubspace.subspaceName) { - subspace.tags = selectedSubspace.tags; + if (selectedSubspaces != null) { + if (subspaces != null) { + for (final subspace in subspaces!) { + for (final selectedSubspace in selectedSubspaces) { + if (subspace.subspaceName == + selectedSubspace.subspaceName) { + subspace.tags = selectedSubspace.tags; + } + } + } } } - } - } - } - }); - }, - ); - }, - ); - } - - List createInitialSelectedProducts( - List? tags, List? subspaces) { - final Map productCounts = {}; - - if (tags != null) { - for (var tag in tags) { - if (tag.product != null) { - productCounts[tag.product!] = (productCounts[tag.product!] ?? 0) + 1; - } - } - } - - if (subspaces != null) { - for (var subspace in subspaces) { - if (subspace.tags != null) { - for (var tag in subspace.tags!) { - if (tag.product != null) { - productCounts[tag.product!] = - (productCounts[tag.product!] ?? 0) + 1; - } - } - } - } - } - - return productCounts.entries - .map((entry) => SelectedProduct( - productId: entry.key.uuid, - count: entry.value, - productName: entry.key.name ?? 'Unnamed', - product: entry.key, - )) - .toList(); - } - - Map _groupTags(List tags) { - final Map groupedTags = {}; - for (var tag in tags) { - if (tag.product != null) { - groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1; - } - } - return groupedTags; + }); + }, + ); + }, + ); } } diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.dart new file mode 100644 index 00000000..1f719b1a --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DuplicateProcessDialog extends StatefulWidget { + final VoidCallback onDuplicate; + + const DuplicateProcessDialog({required this.onDuplicate, Key? key}) + : super(key: key); + + @override + _DuplicateProcessDialogState createState() => _DuplicateProcessDialogState(); +} + +class _DuplicateProcessDialogState extends State { + bool isDuplicating = true; + + @override + void initState() { + super.initState(); + _startDuplication(); + } + + void _startDuplication() async { + WidgetsBinding.instance.addPostFrameCallback((_) { + widget.onDuplicate(); + }); + await Future.delayed(const Duration(seconds: 2)); + setState(() { + isDuplicating = false; + }); + + await Future.delayed(const Duration(seconds: 2)); + if (mounted) { + Navigator.of(context).pop(); + } + } + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + return AlertDialog( + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + content: SizedBox( + width: screenWidth * 0.4, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (isDuplicating) ...[ + const CircularProgressIndicator( + color: ColorsManager.vividBlue, + ), + const SizedBox(height: 15), + Text( + "Duplicating in progress", + style: Theme.of(context) + .textTheme + .headlineSmall + ?.copyWith(color: ColorsManager.primaryColor), + textAlign: TextAlign.center, + ), + ] else ...[ + const Icon( + Icons.check_circle, + color: ColorsManager.vividBlue, + size: 50, + ), + const SizedBox(height: 15), + Text( + "Duplicating successful", + textAlign: TextAlign.center, + style: Theme.of(context) + .textTheme + .headlineSmall + ?.copyWith(color: ColorsManager.primaryColor), + ), + ], + ], + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart index 4a85348f..2d9222a6 100644 --- a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart @@ -46,9 +46,9 @@ class AssignTagBloc extends Bloc { } emit(AssignTagLoaded( - tags: allTags, - isSaveEnabled: _validateTags(allTags), - )); + tags: allTags, + isSaveEnabled: _validateTags(allTags), + errorMessage: '')); }); on((event, emit) { @@ -117,12 +117,10 @@ class AssignTagBloc extends Bloc { } bool _validateTags(List tags) { - if (tags.isEmpty) { - return false; - } final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); - return uniqueTags.length == tags.length && !hasEmptyTag; + final isValid = uniqueTags.length == tags.length && !hasEmptyTag; + return isValid; } String? _getValidationError(List tags) { diff --git a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart index 959c83df..7ec19a45 100644 --- a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart +++ b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/common/dialog_dropdown.dart'; +import 'package:syncrow_web/common/dialog_textfield_dropdown.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/add_device_type/views/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; @@ -79,6 +82,7 @@ class AssignTagDialog extends StatelessWidget { style: Theme.of(context).textTheme.bodyMedium)), DataColumn( + numeric: false, label: Text('Tag', style: Theme.of(context).textTheme.bodyMedium)), @@ -109,10 +113,11 @@ class AssignTagDialog extends StatelessWidget { : List.generate(state.tags.length, (index) { final tag = state.tags[index]; final controller = controllers[index]; - + final availableTags = getAvailableTags( + allTags ?? [], state.tags, tag); return DataRow( cells: [ - DataCell(Text(index.toString())), + DataCell(Text((index + 1).toString())), DataCell( Row( mainAxisAlignment: @@ -123,147 +128,80 @@ class AssignTagDialog extends StatelessWidget { tag.product?.name ?? 'Unknown', overflow: TextOverflow.ellipsis, )), - IconButton( - icon: const Icon(Icons.close, - color: ColorsManager.warningRed, - size: 16), - onPressed: () { - context.read().add( - DeleteTag( - tagToDelete: tag, - tags: state.tags)); - }, - tooltip: 'Delete Tag', - ) - ], - ), - ), - DataCell( - Row( - children: [ - Expanded( - child: TextFormField( - controller: controller, - onChanged: (value) { - context - .read() - .add(UpdateTagEvent( - index: index, - tag: value.trim(), - )); - }, - decoration: const InputDecoration( - hintText: 'Enter Tag', - border: InputBorder.none, - ), - style: const TextStyle( - fontSize: 14, - color: ColorsManager.blackColor, + const SizedBox(width: 10), + Container( + width: 20.0, + height: 20.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: ColorsManager + .lightGrayColor, + width: 1.0, ), ), - ), - SizedBox( - width: MediaQuery.of(context) - .size - .width * - 0.15, - child: PopupMenuButton( - color: ColorsManager.whiteColors, + child: IconButton( icon: const Icon( - Icons.arrow_drop_down, - color: - ColorsManager.blackColor), - onSelected: (value) { - controller.text = value; + Icons.close, + color: ColorsManager + .lightGreyColor, + size: 16, + ), + onPressed: () { context .read() - .add(UpdateTagEvent( - index: index, - tag: value, - )); - }, - itemBuilder: (context) { - return (allTags ?? []) - .where((tagValue) => !state - .tags - .map((e) => e.tag) - .contains(tagValue)) - .map((tagValue) { - return PopupMenuItem( - textStyle: const TextStyle( - color: ColorsManager - .textPrimaryColor), - value: tagValue, - child: ConstrainedBox( - constraints: - BoxConstraints( - minWidth: MediaQuery.of( - context) - .size - .width * - 0.15, - maxWidth: MediaQuery.of( - context) - .size - .width * - 0.15, - ), - child: Text( - tagValue, - overflow: TextOverflow - .ellipsis, - ), - )); - }).toList(); + .add(DeleteTag( + tagToDelete: tag, + tags: state.tags)); }, + tooltip: 'Delete Tag', + padding: EdgeInsets.zero, + constraints: + const BoxConstraints(), ), ), ], ), ), DataCell( - DropdownButtonHideUnderline( - child: DropdownButton( - value: tag.location ?? 'Main', - dropdownColor: ColorsManager - .whiteColors, // Dropdown background - style: const TextStyle( - color: Colors - .black), // Style for selected text - items: [ - const DropdownMenuItem( - value: 'Main Space', - child: Text( - 'Main Space', - style: TextStyle( - color: ColorsManager - .textPrimaryColor), - ), - ), - ...locations.map((location) { - return DropdownMenuItem( - value: location, - child: Text( - location, - style: const TextStyle( - color: ColorsManager - .textPrimaryColor), - ), - ); - }).toList(), - ], - onChanged: (value) { - if (value != null) { + Container( + alignment: Alignment + .centerLeft, // Align cell content to the left + child: SizedBox( + width: double + .infinity, // Ensure full width for dropdown + child: DialogTextfieldDropdown( + items: availableTags, + initialValue: tag.tag, + onSelected: (value) { + controller.text = value; + context + .read() + .add(UpdateTagEvent( + index: index, + tag: value.trim(), + )); + }, + ), + ), + ), + ), + DataCell( + SizedBox( + width: double.infinity, + child: DialogDropdown( + items: locations, + selectedValue: + tag.location ?? 'Main Space', + onSelected: (value) { context .read() .add(UpdateLocation( index: index, location: value, )); - } - }, - ), - ), + }, + )), ), ], ); @@ -284,11 +222,33 @@ class AssignTagDialog extends StatelessWidget { children: [ const SizedBox(width: 10), Expanded( - child: CancelButton( - label: 'Cancel', - onPressed: () async { - Navigator.of(context).pop(); - }, + child: Builder( + builder: (buttonContext) => CancelButton( + label: 'Add New Device', + onPressed: () async { + final updatedTags = List.from(state.tags); + final result = processTags(updatedTags, subspaces); + + final processedTags = + result['updatedTags'] as List; + final processedSubspaces = result['subspaces']; + + Navigator.of(context).pop(); + + await showDialog( + barrierDismissible: false, + context: context, + builder: (dialogContext) => AddDeviceTypeWidget( + products: products, + subspaces: processedSubspaces, + initialSelectedProducts: addedProducts, + allTags: allTags, + spaceName: spaceName, + spaceTags: processedTags, + ), + ); + }, + ), ), ), const SizedBox(width: 10), @@ -302,22 +262,16 @@ class AssignTagDialog extends StatelessWidget { onPressed: state.isSaveEnabled ? () async { Navigator.of(context).pop(); - final assignedTags = {}; - for (var tag in state.tags) { - if (tag.location == null || - subspaces == null) { - continue; - } - for (var subspace in subspaces!) { - if (tag.location == subspace.subspaceName) { - subspace.tags ??= []; - subspace.tags!.add(tag); - assignedTags.add(tag); - break; - } - } - } - onSave!(state.tags,subspaces); + final updatedTags = List.from(state.tags); + final result = + processTags(updatedTags, subspaces); + + final processedTags = + result['updatedTags'] as List; + final processedSubspaces = + result['subspaces'] as List; + + onSave!(processedTags, processedSubspaces); } : null, child: const Text('Save'), @@ -337,4 +291,110 @@ class AssignTagDialog extends StatelessWidget { ), ); } + + List getAvailableTags( + List allTags, List currentTags, Tag currentTag) { + return allTags + .where((tagValue) => !currentTags + .where((e) => e != currentTag) // Exclude the current row + .map((e) => e.tag) + .contains(tagValue)) + .toList(); + } + + Map processTags( + List updatedTags, List? subspaces) { + final modifiedTags = List.from(updatedTags); + final modifiedSubspaces = List.from(subspaces ?? []); + + for (var tag in modifiedTags.toList()) { + if (modifiedSubspaces.isEmpty) continue; + + final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces); + + if ((tag.location == 'Main Space' || tag.location == null) && + (prevIndice == null || + modifiedSubspaces[prevIndice].subspaceName == 'Main Space')) { + continue; + } + + if ((tag.location == 'Main Space' || tag.location == null) && + prevIndice != null) { + modifiedSubspaces[prevIndice] + .tags + ?.removeWhere((t) => t.internalId == tag.internalId); + continue; + } + + if ((tag.location != 'Main Space' && tag.location != null) && + prevIndice == null) { + final newIndex = modifiedSubspaces + .indexWhere((subspace) => subspace.subspaceName == tag.location); + if (newIndex != -1) { + if (modifiedSubspaces[newIndex] + .tags + ?.any((t) => t.internalId == tag.internalId) != + true) { + tag.location = modifiedSubspaces[newIndex].subspaceName; + modifiedSubspaces[newIndex].tags?.add(tag); + } + } + modifiedTags.removeWhere((t) => t.internalId == tag.internalId); + continue; + } + + if ((tag.location != 'Main Space' && tag.location != null) && + tag.location != modifiedSubspaces[prevIndice!].subspaceName) { + modifiedSubspaces[prevIndice] + .tags + ?.removeWhere((t) => t.internalId == tag.internalId); + final newIndex = modifiedSubspaces + .indexWhere((subspace) => subspace.subspaceName == tag.location); + if (newIndex != -1) { + if (modifiedSubspaces[newIndex] + .tags + ?.any((t) => t.internalId == tag.internalId) != + true) { + tag.location = modifiedSubspaces[newIndex].subspaceName; + modifiedSubspaces[newIndex].tags?.add(tag); + } + } + + modifiedTags.removeWhere((t) => t.internalId == tag.internalId); + continue; + } + + if ((tag.location != 'Main Space' && tag.location != null) && + tag.location == modifiedSubspaces[prevIndice!].subspaceName) { + modifiedTags.removeWhere((t) => t.internalId == tag.internalId); + continue; + } + + if ((tag.location == 'Main Space' || tag.location == null) && + prevIndice != null) { + modifiedSubspaces[prevIndice] + .tags + ?.removeWhere((t) => t.internalId == tag.internalId); + } + } + + return { + 'updatedTags': modifiedTags, + 'subspaces': modifiedSubspaces, + }; + } + + int? checkTagExistInSubspace(Tag tag, List? subspaces) { + if (subspaces == null) return null; + for (int i = 0; i < subspaces.length; i++) { + final subspace = subspaces[i]; + if (subspace.tags == null) continue; + for (var t in subspace.tags!) { + if (tag.internalId == t.internalId) { + return i; + } + } + } + return null; + } } diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 8cff1b35..8f9b51d7 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -99,7 +99,6 @@ class AssignTagModelsDialog extends StatelessWidget { .bodyMedium)), DataColumn( numeric: false, - headingRowAlignment: MainAxisAlignment.start, label: Text('Tag', style: Theme.of(context) .textTheme @@ -462,4 +461,5 @@ class AssignTagModelsDialog extends StatelessWidget { 'subspaces': modifiedSubspaces, }; } + } diff --git a/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart b/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart index 5426f8f0..1a1884e2 100644 --- a/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart +++ b/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart @@ -6,7 +6,7 @@ import 'subspace_event.dart'; import 'subspace_state.dart'; class SubSpaceBloc extends Bloc { - SubSpaceBloc() : super(SubSpaceState([], [], '',{})) { + SubSpaceBloc() : super(SubSpaceState([], [], '', {})) { on((event, emit) { final existingNames = state.subSpaces.map((e) => e.subspaceName).toSet(); @@ -26,13 +26,22 @@ class SubSpaceBloc extends Bloc { final updatedSubSpaces = List.from(state.subSpaces) ..add(event.subSpace); - emit(SubSpaceState( - updatedSubSpaces, - state.updatedSubSpaceModels, - '', - state.duplicates, + if (state.duplicates.isNotEmpty) { + emit(SubSpaceState( + updatedSubSpaces, + state.updatedSubSpaceModels, + '*Duplicated sub-space name', + state.duplicates, + )); + } else { + emit(SubSpaceState( + updatedSubSpaces, + state.updatedSubSpaceModels, + '', + state.duplicates, // Clear error message - )); + )); + } } }); @@ -45,6 +54,13 @@ class SubSpaceBloc extends Bloc { state.updatedSubSpaceModels, ); + if (event.subSpace.uuid?.isNotEmpty ?? false) { + updatedSubspaceModels.add(UpdateSubspaceModel( + action: Action.delete, + uuid: event.subSpace.uuid!, + )); + } + final nameOccurrences = {}; for (final subSpace in updatedSubSpaces) { final lowerName = subSpace.subspaceName.toLowerCase(); @@ -55,19 +71,17 @@ class SubSpaceBloc extends Bloc { .where((entry) => entry.value > 1) .map((entry) => entry.key) .toSet(); - if (event.subSpace.uuid?.isNotEmpty ?? false) { - updatedSubspaceModels.add(UpdateSubspaceModel( - action: Action.delete, - uuid: event.subSpace.uuid!, - )); - } + final errorMessage = + updatedDuplicates.isNotEmpty ? '*Duplicated sub-space name' : ''; - emit(SubSpaceState(updatedSubSpaces, updatedSubspaceModels, '', - updatedDuplicates // Clear error message - )); + emit(SubSpaceState( + updatedSubSpaces, + updatedSubspaceModels, + errorMessage, + updatedDuplicates, + )); }); - // Handle UpdateSubSpace Event on((event, emit) { final updatedSubSpaces = state.subSpaces.map((subSpace) { diff --git a/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart index 1e8d0ddc..a331aed2 100644 --- a/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart +++ b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart @@ -25,22 +25,30 @@ class SubSpaceModelBloc extends Bloc { updatedDuplicates, )); } else { - // Add subspace if no duplicate exists final updatedSubSpaces = List.from(state.subSpaces) ..add(event.subSpace); - emit(SubSpaceModelState( - updatedSubSpaces, - state.updatedSubSpaceModels, - '', - state.duplicates, -// Clear error message - )); + if (state.duplicates.isNotEmpty) { + emit(SubSpaceModelState( + updatedSubSpaces, + state.updatedSubSpaceModels, + '*Duplicated sub-space name', + state.duplicates, + )); + } else { + emit(SubSpaceModelState( + updatedSubSpaces, + state.updatedSubSpaceModels, + '', + state.duplicates, + )); + } } }); // Handle RemoveSubSpaceModel Event + on((event, emit) { final updatedSubSpaces = List.from(state.subSpaces) ..remove(event.subSpace); @@ -48,16 +56,6 @@ class SubSpaceModelBloc extends Bloc { final updatedSubspaceModels = List.from( state.updatedSubSpaceModels, ); - final nameOccurrences = {}; - for (final subSpace in updatedSubSpaces) { - final lowerName = subSpace.subspaceName.toLowerCase(); - nameOccurrences[lowerName] = (nameOccurrences[lowerName] ?? 0) + 1; - } - - final updatedDuplicates = nameOccurrences.entries - .where((entry) => entry.value > 1) - .map((entry) => entry.key) - .toSet(); if (event.subSpace.uuid?.isNotEmpty ?? false) { updatedSubspaceModels.add(UpdateSubspaceTemplateModel( @@ -66,12 +64,28 @@ class SubSpaceModelBloc extends Bloc { )); } + // Count occurrences of sub-space names to identify duplicates + final nameOccurrences = {}; + for (final subSpace in updatedSubSpaces) { + final lowerName = subSpace.subspaceName.toLowerCase(); + nameOccurrences[lowerName] = (nameOccurrences[lowerName] ?? 0) + 1; + } + + // Identify duplicate names + final updatedDuplicates = nameOccurrences.entries + .where((entry) => entry.value > 1) + .map((entry) => entry.key) + .toSet(); + + // Determine the error message + final errorMessage = + updatedDuplicates.isNotEmpty ? '*Duplicated sub-space name' : ''; + emit(SubSpaceModelState( updatedSubSpaces, updatedSubspaceModels, - '', + errorMessage, updatedDuplicates, -// Clear error message )); }); diff --git a/lib/pages/spaces_management/helper/tag_helper.dart b/lib/pages/spaces_management/helper/tag_helper.dart index 4fa86b88..041f005f 100644 --- a/lib/pages/spaces_management/helper/tag_helper.dart +++ b/lib/pages/spaces_management/helper/tag_helper.dart @@ -1,6 +1,8 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; @@ -30,7 +32,36 @@ class TagHelper { } } } - + + return initialTags; + } + + static List generateInitialForTags({ + List? spaceTags, + List? subspaces, + }) { + final List initialTags = []; + + if (spaceTags != null) { + initialTags.addAll(spaceTags); + } + + if (subspaces != null) { + for (var subspace in subspaces) { + if (subspace.tags != null) { + initialTags.addAll( + subspace.tags!.map( + (tag) => tag.copyWith( + location: subspace.subspaceName, + internalId: tag.internalId, + tag: tag.tag, + ), + ), + ); + } + } + } + return initialTags; } @@ -79,4 +110,39 @@ class TagHelper { )) .toList(); } + + static List createInitialSelectedProductsForTags( + List? tags, List? subspaces) { + final Map productCounts = {}; + + if (tags != null) { + for (var tag in tags) { + if (tag.product != null) { + productCounts[tag.product!] = (productCounts[tag.product!] ?? 0) + 1; + } + } + } + + if (subspaces != null) { + for (var subspace in subspaces) { + if (subspace.tags != null) { + for (var tag in subspace.tags!) { + if (tag.product != null) { + productCounts[tag.product!] = + (productCounts[tag.product!] ?? 0) + 1; + } + } + } + } + } + + return productCounts.entries + .map((entry) => SelectedProduct( + productId: entry.key.uuid, + count: entry.value, + productName: entry.key.name ?? 'Unnamed', + product: entry.key, + )) + .toList(); + } } diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart index 740ff832..36efaaa5 100644 --- a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -201,106 +201,101 @@ class CreateSpaceModelBloc on((event, emit) async { try { - final prevSpaceModel = event.spaceTemplate; - final newSpaceModel = event.updatedSpaceTemplate; - String? spaceModelName; - if (prevSpaceModel.modelName != newSpaceModel.modelName) { - spaceModelName = newSpaceModel.modelName; - } - List tagUpdates = []; - final List subspaceUpdates = []; - final List? prevSubspaces = - prevSpaceModel.subspaceModels; - final List? newSubspaces = - newSpaceModel.subspaceModels; + if (event.spaceTemplate.uuid != null) { + final prevSpaceModel = + await _api.getSpaceModel(event.spaceTemplate.uuid ?? ''); - tagUpdates = processTagUpdates(prevSpaceModel.tags, newSpaceModel.tags); + final newSpaceModel = event.updatedSpaceTemplate; + String? spaceModelName; + if (prevSpaceModel?.modelName != newSpaceModel.modelName) { + spaceModelName = newSpaceModel.modelName; + } + List tagUpdates = []; + final List subspaceUpdates = []; + final List? prevSubspaces = + prevSpaceModel?.subspaceModels; + final List? newSubspaces = + newSpaceModel.subspaceModels; - if (prevSubspaces != null || newSubspaces != null) { - if (prevSubspaces != null && newSubspaces != null) { - for (var prevSubspace in prevSubspaces) { - final existsInNew = newSubspaces - .any((newTag) => newTag.uuid == prevSubspace.uuid); - if (!existsInNew) { + tagUpdates = + processTagUpdates(prevSpaceModel?.tags, newSpaceModel.tags); + + if (prevSubspaces != null || newSubspaces != null) { + if (prevSubspaces != null && newSubspaces != null) { + for (var prevSubspace in prevSubspaces) { + final existsInNew = newSubspaces + .any((subspace) => subspace.uuid == prevSubspace.uuid); + if (!existsInNew) { + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.delete, uuid: prevSubspace.uuid)); + } + } + } else if (prevSubspaces != null && newSubspaces == null) { + for (var prevSubspace in prevSubspaces) { subspaceUpdates.add(UpdateSubspaceTemplateModel( action: Action.delete, uuid: prevSubspace.uuid)); } } - } else if (prevSubspaces != null && newSubspaces == null) { - for (var prevSubspace in prevSubspaces) { - subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.delete, uuid: prevSubspace.uuid)); - } - } - if (newSubspaces != null) { - for (var newSubspace in newSubspaces!) { - // Tag without UUID - if ((newSubspace.uuid == null || newSubspace.uuid!.isEmpty)) { - final List tagUpdates = []; + if (newSubspaces != null) { + for (var newSubspace in newSubspaces!) { + // Tag without UUID + if ((newSubspace.uuid == null || newSubspace.uuid!.isEmpty)) { + final List tagUpdates = []; - if (newSubspace.tags != null) { - for (var tag in newSubspace.tags!) { - tagUpdates.add(TagModelUpdate( - action: Action.add, - uuid: tag.uuid == '' ? null : tag.uuid, - tag: tag.tag, - productUuid: tag.product?.uuid)); + if (newSubspace.tags != null) { + for (var tag in newSubspace.tags!) { + tagUpdates.add(TagModelUpdate( + action: Action.add, + uuid: tag.uuid == '' ? null : tag.uuid, + tag: tag.tag, + productUuid: tag.product?.uuid)); + } } + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.add, + subspaceName: newSubspace.subspaceName, + tags: tagUpdates)); + } + } + } + + if (prevSubspaces != null && newSubspaces != null) { + final newSubspaceMap = { + for (var subspace in newSubspaces!) subspace.uuid: subspace + }; + + for (var prevSubspace in prevSubspaces) { + final newSubspace = newSubspaceMap[prevSubspace.uuid]; + + if (newSubspace != null) { + final List tagSubspaceUpdates = + processTagUpdates(prevSubspace.tags, newSubspace.tags); + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.update, + uuid: newSubspace.uuid, + subspaceName: newSubspace.subspaceName, + tags: tagSubspaceUpdates)); } - subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.add, - subspaceName: newSubspace.subspaceName, - tags: tagUpdates)); } } } - if (prevSubspaces != null && newSubspaces != null) { - final newSubspaceMap = { - for (var subspace in newSubspaces!) subspace.uuid: subspace - }; + final spaceModelBody = CreateSpaceTemplateBodyModel( + modelName: spaceModelName, + tags: tagUpdates, + subspaceModels: subspaceUpdates); - for (var prevSubspace in prevSubspaces) { - final newSubspace = newSubspaceMap[prevSubspace.uuid]; + final res = await _api.updateSpaceModel( + spaceModelBody, prevSpaceModel?.uuid ?? ''); - if (newSubspace != null) { - if(prevSubspace.tags!=null){ - for(var t in prevSubspace.tags!){ - print("old tags are ${t.tag} ${t.uuid}"); - }} - - if(newSubspace.tags!=null){ - for(var t in newSubspace.tags!){ - print("new tags are ${t.tag} ${t.uuid}"); - }} - - final List tagSubspaceUpdates = - processTagUpdates(prevSubspace.tags, newSubspace.tags); - subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.update, - uuid: newSubspace.uuid, - subspaceName: newSubspace.subspaceName, - tags: tagSubspaceUpdates)); - } + if (res != null) { + emit(CreateSpaceModelLoaded(newSpaceModel)); + if (event.onUpdate != null) { + event.onUpdate!(event.updatedSpaceTemplate); } } } - - final spaceModelBody = CreateSpaceTemplateBodyModel( - modelName: spaceModelName, - tags: tagUpdates, - subspaceModels: subspaceUpdates); - - final res = await _api.updateSpaceModel( - spaceModelBody, prevSpaceModel.uuid ?? ''); - - if (res != null) { - emit(CreateSpaceModelLoaded(newSpaceModel)); - if (event.onUpdate != null) { - event.onUpdate!(event.updatedSpaceTemplate); - } - } } catch (e) { emit(CreateSpaceModelError('Error creating space model')); } @@ -314,6 +309,18 @@ class CreateSpaceModelBloc final List tagUpdates = []; final processedTags = {}; + if (prevTags == null && newTags != null) { + for (var newTag in newTags) { + tagUpdates.add(TagModelUpdate( + action: Action.add, + tag: newTag.tag, + uuid: newTag.uuid, + productUuid: newTag.product?.uuid, + )); + } + return tagUpdates; + } + if (newTags != null || prevTags != null) { // Case 1: Tags deleted if (prevTags != null && newTags != null) { @@ -334,9 +341,11 @@ class CreateSpaceModelBloc // Case 2: Tags added if (newTags != null) { + final prevTagUuids = prevTags?.map((t) => t.uuid).toSet() ?? {}; + for (var newTag in newTags!) { // Tag without UUID - if ((newTag.uuid == null || newTag.uuid!.isEmpty) && + if ((newTag.uuid == null || !prevTagUuids.contains(newTag.uuid)) && !processedTags.contains(newTag.tag)) { tagUpdates.add(TagModelUpdate( action: Action.add, diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 301365ed..4d3dbb0c 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -70,5 +70,5 @@ abstract class ColorsManager { static const Color invitedOrangeText = Color(0xFFFFBF00); static const Color lightGrayBorderColor = Color(0xB2D5D5D5); //background: #F8F8F8; - + static const Color vividBlue = Color(0xFF023DFE); } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index b151890a..d5d216c5 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -399,5 +399,7 @@ class Assets { static const String ZtoAIcon = 'assets/icons/ztoa_icon.png'; static const String AtoZIcon = 'assets/icons/atoz_icon.png'; 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'; } //user_management.svg From 812dc4792bb37d3cfec2387addc4d1e16130d6cb Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 27 Jan 2025 01:19:08 +0400 Subject: [PATCH 134/175] fixed duplicate --- .../widgets/community_structure_widget.dart | 109 +++++++++--------- 1 file changed, 55 insertions(+), 54 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index b5f54708..8e8cbe8f 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -633,29 +633,37 @@ class _CommunityStructureAreaState extends State { void _duplicateSpace(SpaceModel space) { final Map originalToDuplicate = {}; - const double horizontalGap = 150.0; + const double horizontalGap = 200.0; const double verticalGap = 100.0; - SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition) { - // Find a new position for the duplicated space + final Map nameCounters = {}; + + String _generateCopyName(String originalName) { + final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim(); + nameCounters[baseName] = (nameCounters[baseName] ?? 0) + 1; + return "$baseName(${nameCounters[baseName]})"; + } + + SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition, + SpaceModel? duplicatedParent) { Offset newPosition = parentPosition + Offset(horizontalGap, 0); - // Avoid overlapping with existing spaces while (spaces.any((s) => (s.position - newPosition).distance < horizontalGap && s.status != SpaceStatus.deleted)) { newPosition += Offset(horizontalGap, 0); } - // Create the duplicated space + final duplicatedName = _generateCopyName(original.name); + final duplicated = SpaceModel( - name: "${original.name} (Copy)", + name: duplicatedName, icon: original.icon, position: newPosition, isPrivate: original.isPrivate, children: [], status: SpaceStatus.newSpace, - parent: original.parent, + parent: duplicatedParent, spaceModel: original.spaceModel, subspaces: original.subspaces, tags: original.tags, @@ -663,62 +671,55 @@ class _CommunityStructureAreaState extends State { originalToDuplicate[original] = duplicated; - // Copy the children of the original space to the duplicated space + setState(() { + spaces.add(duplicated); + _updateNodePosition(duplicated, duplicated.position); + + if (duplicatedParent != null) { + final newConnection = Connection( + startSpace: duplicatedParent, + endSpace: duplicated, + direction: "down", + ); + connections.add(newConnection); + duplicated.incomingConnection = newConnection; + duplicatedParent.addOutgoingConnection(newConnection); + } + + if (original.parent != null && duplicatedParent == null) { + final originalParent = original.parent!; + final duplicatedParent = + originalToDuplicate[originalParent] ?? originalParent; + + final parentConnection = Connection( + startSpace: duplicatedParent, + endSpace: duplicated, + direction: original.incomingConnection?.direction ?? "down", + ); + + connections.add(parentConnection); + duplicated.incomingConnection = parentConnection; + duplicatedParent.addOutgoingConnection(parentConnection); + } + }); + Offset childStartPosition = newPosition + Offset(0, verticalGap); for (final child in original.children) { - final duplicatedChild = duplicateRecursive(child, childStartPosition); + final duplicatedChild = + duplicateRecursive(child, childStartPosition, duplicated); duplicated.children.add(duplicatedChild); - duplicatedChild.parent = - duplicated; // Set the parent for the duplicated child childStartPosition += Offset(0, verticalGap); } return duplicated; } - // Duplicate the selected space and its children - final duplicatedSpace = duplicateRecursive(space, space.position); - - // Ensure the duplicated space has the same parent as the original - if (space.parent != null) { - final parentSpace = space.parent!; - final duplicatedParent = originalToDuplicate[parentSpace] ?? parentSpace; - duplicatedSpace.parent = duplicatedParent; - duplicatedParent.children.add(duplicatedSpace); + if (space.parent == null) { + duplicateRecursive(space, space.position, null); + } else { + final duplicatedParent = + originalToDuplicate[space.parent!] ?? space.parent!; + duplicateRecursive(space, space.position, duplicatedParent); } - - // Flatten the hierarchy of the duplicated spaces - List flattenHierarchy(SpaceModel root) { - final List result = []; - void traverse(SpaceModel node) { - result.add(node); - for (final child in node.children) { - traverse(child); - } - } - - traverse(root); - return result; - } - - final duplicatedSpacesList = flattenHierarchy(duplicatedSpace); - - setState(() { - spaces.addAll(duplicatedSpacesList); - - // Duplicate the connections - for (final connection in connections) { - if (originalToDuplicate.containsKey(connection.startSpace) && - originalToDuplicate.containsKey(connection.endSpace)) { - connections.add( - Connection( - startSpace: originalToDuplicate[connection.startSpace]!, - endSpace: originalToDuplicate[connection.endSpace]!, - direction: connection.direction, - ), - ); - } - } - }); } } From d0c6b130720783c6584f6d9b76c96047287b2e12 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 27 Jan 2025 01:25:04 +0400 Subject: [PATCH 135/175] updated edit flow --- .../widgets/community_structure_widget.dart | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 8e8cbe8f..4d470c55 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -137,7 +137,7 @@ class _CommunityStructureAreaState extends State { selectedSpace: widget.selectedSpace, onDelete: _onDelete, onDuplicate: () => {_onDuplicate(context)}, - onEdit: () => {}, + onEdit: () => {_showEditSpaceDialog()}, onEditName: () { setState(() { isEditingName = !isEditingName; @@ -214,9 +214,6 @@ class _CommunityStructureAreaState extends State { opacity: isHighlighted ? 1.0 : 0.3, child: SpaceContainerWidget( index: index, - onDoubleTap: () { - _showEditSpaceDialog(spaces[index]); - }, onTap: () { _selectSpace(context, spaces[index]); }, @@ -342,40 +339,43 @@ class _CommunityStructureAreaState extends State { ); } - void _showEditSpaceDialog(SpaceModel space) { - showDialog( - context: context, - builder: (BuildContext context) { - return CreateSpaceDialog( - products: widget.products, - spaceModels: widget.spaceModels, - name: space.name, - icon: space.icon, - editSpace: space, - isEdit: true, - onCreateSpace: (String name, - String icon, - List selectedProducts, - SpaceTemplateModel? spaceModel, - List? subspaces, - List? tags) { - setState(() { - // Update the space's properties - space.name = name; - space.icon = icon; - space.spaceModel = spaceModel; - space.subspaces = subspaces; - space.tags = tags; + void _showEditSpaceDialog() { + if (widget.selectedSpace != null) { + showDialog( + context: context, + builder: (BuildContext context) { + return CreateSpaceDialog( + products: widget.products, + spaceModels: widget.spaceModels, + name: widget.selectedSpace!.name, + icon: widget.selectedSpace!.icon, + editSpace: widget.selectedSpace, + isEdit: true, + onCreateSpace: (String name, + String icon, + List selectedProducts, + SpaceTemplateModel? spaceModel, + List? subspaces, + List? tags) { + setState(() { + // Update the space's properties + widget.selectedSpace!.name = name; + widget.selectedSpace!.icon = icon; + widget.selectedSpace!.spaceModel = spaceModel; + widget.selectedSpace!.subspaces = subspaces; + widget.selectedSpace!.tags = tags; - if (space.status != SpaceStatus.newSpace) { - space.status = SpaceStatus.modified; // Mark as modified - } - }); - }, - key: Key(space.name), - ); - }, - ); + if (widget.selectedSpace!.status != SpaceStatus.newSpace) { + widget.selectedSpace!.status = + SpaceStatus.modified; // Mark as modified + } + }); + }, + key: Key(widget.selectedSpace!.name), + ); + }, + ); + } } void _handleHoverChanged(int index, bool isHovered) { From c72297e0c8b8da27aef907cb19c6da02e356b372 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 27 Jan 2025 14:21:07 +0400 Subject: [PATCH 136/175] updated the duplicate --- .../widgets/community_structure_widget.dart | 22 ++++++++++++++++++- .../views/assign_tag_models_dialog.dart | 10 ++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 4d470c55..44122a06 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -703,8 +703,28 @@ class _CommunityStructureAreaState extends State { } }); - Offset childStartPosition = newPosition + Offset(0, verticalGap); + final childrenWithDownDirection = original.children + .where((child) => + child.incomingConnection?.direction == "down" && + child.status != SpaceStatus.deleted) + .toList(); + + Offset childStartPosition = childrenWithDownDirection.length == 1 + ? duplicated.position + : newPosition + Offset(0, verticalGap); + for (final child in original.children) { + final isDownDirection = + child.incomingConnection?.direction == "down" ?? false; + + if (isDownDirection && childrenWithDownDirection.length == 1) { + // Place the only "down" child vertically aligned with the parent + childStartPosition = duplicated.position + Offset(0, verticalGap); + } else if (!isDownDirection) { + // Position children with other directions horizontally + childStartPosition = duplicated.position + Offset(horizontalGap, 0); + } + final duplicatedChild = duplicateRecursive(child, childStartPosition, duplicated); duplicated.children.add(duplicatedChild); diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 8f9b51d7..31dba875 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -385,6 +385,15 @@ class AssignTagModelsDialog extends StatelessWidget { final modifiedTags = List.from(updatedTags); final modifiedSubspaces = List.from(subspaces ?? []); + if (subspaces != null) { + for (var subspace in subspaces) { + subspace.tags?.removeWhere( + (tag) => !modifiedTags + .any((updatedTag) => updatedTag.internalId == tag.internalId), + ); + } + } + for (var tag in modifiedTags.toList()) { if (modifiedSubspaces.isEmpty) continue; @@ -461,5 +470,4 @@ class AssignTagModelsDialog extends StatelessWidget { 'subspaces': modifiedSubspaces, }; } - } From 788fb75a687b7618f755c3fb2ad60ed54d2af15e Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 27 Jan 2025 18:12:57 +0400 Subject: [PATCH 137/175] fixed UI alignment --- .../widgets/dialogs/create_space_dialog.dart | 89 ++++++++++--------- 1 file changed, 48 insertions(+), 41 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 2b8d4aaf..715e8b55 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -149,50 +149,53 @@ class CreateSpaceDialogState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - TextField( - controller: nameController, - onChanged: (value) { - enteredName = value.trim(); - setState(() { - isNameFieldExist = false; - isOkButtonEnabled = false; - isNameFieldInvalid = value.isEmpty; + SizedBox( + width: screenWidth * 0.25, + child: TextField( + controller: nameController, + onChanged: (value) { + enteredName = value.trim(); + setState(() { + isNameFieldExist = false; + isOkButtonEnabled = false; + isNameFieldInvalid = value.isEmpty; - if (!isNameFieldInvalid) { - if (_isNameConflict(value)) { - isNameFieldExist = true; - isOkButtonEnabled = false; - } else { - isNameFieldExist = false; - isOkButtonEnabled = true; + if (!isNameFieldInvalid) { + if (_isNameConflict(value)) { + isNameFieldExist = true; + isOkButtonEnabled = false; + } else { + isNameFieldExist = false; + isOkButtonEnabled = true; + } } - } - }); - }, - style: const TextStyle(color: Colors.black), - decoration: InputDecoration( - hintText: 'Please enter the name', - hintStyle: const TextStyle( - fontSize: 14, - color: ColorsManager.lightGrayColor, - fontWeight: FontWeight.w400, - ), - filled: true, - fillColor: ColorsManager.boxColor, - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: BorderSide( - color: isNameFieldInvalid || isNameFieldExist - ? ColorsManager.red - : ColorsManager.boxColor, - width: 1.5, + }); + }, + style: const TextStyle(color: Colors.black), + decoration: InputDecoration( + hintText: 'Please enter the name', + hintStyle: const TextStyle( + fontSize: 14, + color: ColorsManager.lightGrayColor, + fontWeight: FontWeight.w400, ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: const BorderSide( - color: ColorsManager.boxColor, - width: 1.5, + filled: true, + fillColor: ColorsManager.boxColor, + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: isNameFieldInvalid || isNameFieldExist + ? ColorsManager.red + : ColorsManager.boxColor, + width: 1.5, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: const BorderSide( + color: ColorsManager.boxColor, + width: 1.5, + ), ), ), ), @@ -222,6 +225,9 @@ class CreateSpaceDialogState extends State { const SizedBox(height: 10), selectedSpaceModel == null ? TextButton( + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + ), onPressed: () { _showLinkSpaceModelDialog(context); }, @@ -309,6 +315,7 @@ class CreateSpaceDialogState extends State { subspaces == null ? TextButton( style: TextButton.styleFrom( + padding: EdgeInsets.zero, overlayColor: ColorsManager.transparentColor, ), onPressed: () async { From e61cfd7e49091dab328825320f5873491ab41efb Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 28 Jan 2025 10:48:14 +0400 Subject: [PATCH 138/175] added option to update subspace --- .../widgets/dialogs/create_space_dialog.dart | 19 ++- .../dialog/create_space_model_dialog.dart | 1 - .../widgets/subspace_model_create_widget.dart | 61 +++++++-- .../widgets/subspace_name_label_widget.dart | 121 +++++++++++++++--- 4 files changed, 170 insertions(+), 32 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 715e8b55..50d73fff 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -344,10 +344,21 @@ class CreateSpaceDialogState extends State { runSpacing: 8.0, children: [ if (subspaces != null) - ...subspaces!.map( - (subspace) => SubspaceNameDisplayWidget( - text: subspace.subspaceName, - )), + ...subspaces!.map((subspace) => + SubspaceNameDisplayWidget( + validateName: (updatedName) { + return !subspaces!.any((s) => + s != subspace && + s.subspaceName == updatedName); + }, + text: subspace.subspaceName, + onNameChanged: (updatedName) { + setState(() { + subspace.subspaceName = + updatedName; + }); + }, + )), EditChip( onTap: () async { _showSubSpaceDialog(context, enteredName, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index a825e868..e172231e 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -123,7 +123,6 @@ class CreateSpaceModelDialog extends StatelessWidget { ), const SizedBox(height: 16), SubspaceModelCreate( - context, subspaces: state.space.subspaceModels ?? [], onSpaceModelUpdate: (updatedSubspaces) { context diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 8dc981da..5fddfe6e 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -6,20 +6,36 @@ import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/ import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class SubspaceModelCreate extends StatelessWidget { +class SubspaceModelCreate extends StatefulWidget { final List subspaces; final void Function(List newSubspaces)? onSpaceModelUpdate; - const SubspaceModelCreate(BuildContext context, - {Key? key, required this.subspaces, this.onSpaceModelUpdate}) - : super(key: key); + const SubspaceModelCreate({ + Key? key, + required this.subspaces, + this.onSpaceModelUpdate, + }) : super(key: key); + + @override + _SubspaceModelCreateState createState() => _SubspaceModelCreateState(); +} + +class _SubspaceModelCreateState extends State { + late List _subspaces; + String? errorSubspaceId; + + @override + void initState() { + super.initState(); + _subspaces = List.from(widget.subspaces); + } @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; return Container( - child: subspaces.isEmpty + child: _subspaces.isEmpty ? TextButton( style: TextButton.styleFrom( overlayColor: ColorsManager.transparentColor, @@ -41,16 +57,33 @@ class SubspaceModelCreate extends StatelessWidget { borderRadius: BorderRadius.circular(15), border: Border.all( color: ColorsManager.textFieldGreyColor, - width: 3.0, // Border width + width: 3.0, ), ), child: Wrap( spacing: 8.0, runSpacing: 8.0, children: [ - ...subspaces.map((subspace) => SubspaceNameDisplayWidget( - text: subspace.subspaceName, - )), + ..._subspaces.map((subspace) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SubspaceNameDisplayWidget( + text: subspace.subspaceName, + validateName: (updatedName) { + return !_subspaces.any((s) => + s != subspace && + s.subspaceName == updatedName); + }, + onNameChanged: (updatedName) { + setState(() { + subspace.subspaceName = updatedName; + }); + }, + ), + ], + ); + }), EditChip( onTap: () async { await _openDialog(context, 'Edit Sub-space'); @@ -71,9 +104,15 @@ class SubspaceModelCreate extends StatelessWidget { return CreateSubSpaceModelDialog( isEdit: true, dialogTitle: dialogTitle, - existingSubSpaces: subspaces, + existingSubSpaces: _subspaces, onUpdate: (subspaceModels) { - onSpaceModelUpdate!(subspaceModels); + setState(() { + _subspaces = subspaceModels; + errorSubspaceId = null; + }); + if (widget.onSpaceModelUpdate != null) { + widget.onSpaceModelUpdate!(subspaceModels); + } }, ); }, diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart index a2920b89..2ecbf0b1 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart @@ -1,13 +1,15 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class SubspaceNameDisplayWidget extends StatelessWidget { +class SubspaceNameDisplayWidget extends StatefulWidget { final String text; final TextStyle? textStyle; final Color backgroundColor; final Color borderColor; final EdgeInsetsGeometry padding; final BorderRadiusGeometry borderRadius; + final void Function(String updatedName) onNameChanged; + final bool Function(String updatedName) validateName; const SubspaceNameDisplayWidget({ Key? key, @@ -17,25 +19,112 @@ class SubspaceNameDisplayWidget extends StatelessWidget { this.borderColor = ColorsManager.transparentColor, this.padding = const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), this.borderRadius = const BorderRadius.all(Radius.circular(10)), + required this.onNameChanged, + required this.validateName, }) : super(key: key); + @override + _SubspaceNameDisplayWidgetState createState() => + _SubspaceNameDisplayWidgetState(); +} + +class _SubspaceNameDisplayWidgetState extends State { + bool isEditing = false; + late TextEditingController _controller; + late FocusNode _focusNode; + late String previousName; + String? errorText; + + @override + void initState() { + super.initState(); + _controller = TextEditingController(text: widget.text); + _focusNode = FocusNode(); + previousName = widget.text; + } + + @override + void dispose() { + _controller.dispose(); + _focusNode.dispose(); + super.dispose(); + } + + void _handleValidationAndSave() { + final updatedName = _controller.text; + if (widget.validateName(updatedName)) { + setState(() { + errorText = null; + isEditing = false; + previousName = updatedName; + widget.onNameChanged(updatedName); + }); + } else { + setState(() { + errorText = 'Subspace name already exists.'; + }); + } + } + @override Widget build(BuildContext context) { - return Container( - padding: padding, - decoration: BoxDecoration( - color: backgroundColor, - borderRadius: borderRadius, - border: Border.all(color: borderColor), - ), - child: Text( - text, - style: textStyle ?? - Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: ColorsManager.spaceColor), - ), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + onTap: () { + setState(() { + isEditing = true; + _focusNode.requestFocus(); + }); + }, + child: Container( + padding: widget.padding, + decoration: BoxDecoration( + color: widget.backgroundColor, + borderRadius: widget.borderRadius, + border: Border.all(color: widget.borderColor), + ), + child: isEditing + ? TextField( + controller: _controller, + focusNode: _focusNode, + style: widget.textStyle ?? + Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.spaceColor), + autofocus: true, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 8.0), + ), + onSubmitted: (value) { + _handleValidationAndSave(); + }, + ) + : Text( + widget.text, + style: widget.textStyle ?? + Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.spaceColor), + ), + ), + ), + if (errorText != null) + Padding( + padding: const EdgeInsets.only(top: 4.0), + child: Text( + errorText!, + style: const TextStyle( + color: ColorsManager.warningRed, + fontSize: 12, + ), + ), + ), + ], ); } } From 4b7f4d42791476c34978ca4131da349031b800a9 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 28 Jan 2025 11:39:34 +0400 Subject: [PATCH 139/175] fixed calculating products on add another device --- .../assign_tag_models/views/assign_tag_models_dialog.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 31dba875..90843aa2 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -15,6 +15,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; class AssignTagModelsDialog extends StatelessWidget { final List? products; @@ -258,7 +259,6 @@ class AssignTagModelsDialog extends StatelessWidget { final processedTags = result['updatedTags'] as List; final processedSubspaces = result['subspaces']; - if (context.mounted) { Navigator.of(context).pop(); @@ -270,8 +270,10 @@ class AssignTagModelsDialog extends StatelessWidget { products: products, subspaces: subspaces, isCreate: false, - initialSelectedProducts: - addedProducts, + initialSelectedProducts: TagHelper + .createInitialSelectedProducts( + processedTags, + processedSubspaces), allTags: allTags, spaceName: spaceName, otherSpaceModels: otherSpaceModels, From 9091af26617beb5402d487c1df1a0a13058fb5be Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 28 Jan 2025 13:27:05 +0400 Subject: [PATCH 140/175] fixed text style --- lib/common/edit_chip.dart | 2 +- .../dialog/create_space_model_dialog.dart | 25 +++++++++++++------ .../widgets/subspace_name_label_widget.dart | 8 +++--- .../widgets/tag_chips_display_widget.dart | 11 +++++--- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/lib/common/edit_chip.dart b/lib/common/edit_chip.dart index 7607834d..1643b414 100644 --- a/lib/common/edit_chip.dart +++ b/lib/common/edit_chip.dart @@ -26,7 +26,7 @@ class EditChip extends StatelessWidget { child: Chip( label: Text( label, - style: TextStyle(color: labelColor), + style: Theme.of(context).textTheme.bodySmall!.copyWith(color: labelColor) ), backgroundColor: backgroundColor, shape: RoundedRectangleBorder( diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index e172231e..2fb2a5b0 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -102,14 +102,19 @@ class CreateSpaceModelDialog extends StatelessWidget { name: value, allModels: otherSpaceModels ?? [])); }, - style: const TextStyle(color: ColorsManager.blackColor), + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: ColorsManager.blackColor), decoration: InputDecoration( filled: true, fillColor: ColorsManager.textFieldGreyColor, hintText: 'Please enter the name', errorText: state.errorMessage, - hintStyle: const TextStyle( - color: ColorsManager.lightGrayColor), + hintStyle: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: ColorsManager.lightGrayColor), border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none, @@ -159,7 +164,9 @@ class CreateSpaceModelDialog extends StatelessWidget { const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: (state.errorMessage == null) + onPressed: ((state.errorMessage != null && + state.errorMessage != '') || + !isNameValid) ? () { final updatedSpaceTemplate = updatedSpaceModel.copyWith( @@ -237,8 +244,9 @@ class CreateSpaceModelDialog extends StatelessWidget { : null, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, - foregroundColor: (state.errorMessage != null && - state.errorMessage != '') + foregroundColor: ((state.errorMessage != null && + state.errorMessage != '') || + !isNameValid) ? ColorsManager.whiteColorsWithOpacity : ColorsManager.whiteColors, child: const Text('OK'), @@ -252,7 +260,10 @@ class CreateSpaceModelDialog extends StatelessWidget { } else if (state is CreateSpaceModelError) { return Text( 'Error: ${state.message}', - style: const TextStyle(color: ColorsManager.warningRed), + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: ColorsManager.warningRed), ); } diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart index 2ecbf0b1..63edcf8f 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart @@ -118,10 +118,10 @@ class _SubspaceNameDisplayWidgetState extends State { padding: const EdgeInsets.only(top: 4.0), child: Text( errorText!, - style: const TextStyle( - color: ColorsManager.warningRed, - fontSize: 12, - ), + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: ColorsManager.warningRed), ), ), ], diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 11c7ca5b..a07f9b29 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -73,9 +73,12 @@ class TagChipDisplay extends StatelessWidget { ), label: Text( 'x${entry.value}', // Show count - style: const TextStyle( - color: ColorsManager.spaceColor, - ), + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + color: + ColorsManager.spaceColor), ), backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( @@ -89,7 +92,7 @@ class TagChipDisplay extends StatelessWidget { EditChip(onTap: () async { // Use the Navigator's context for showDialog Navigator.of(context).pop(); - + await showDialog( barrierDismissible: false, context: context, From 45b0b67fe0bb9973f579bae9d6f65e1c599d34ce Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 29 Jan 2025 09:53:06 +0400 Subject: [PATCH 141/175] make counter widget work only for add device --- .../views/add_device_type_widget.dart | 49 +++++-------------- .../widgets/device_type_tile_widget.dart | 13 ++--- .../widgets/scrollable_grid_view_widget.dart | 14 +++--- 3 files changed, 27 insertions(+), 49 deletions(-) diff --git a/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart b/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart index b26dbcc7..1930963b 100644 --- a/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart +++ b/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart @@ -12,7 +12,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_mod import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; -import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class AddDeviceTypeWidget extends StatelessWidget { @@ -23,10 +22,12 @@ class AddDeviceTypeWidget extends StatelessWidget { final List? spaceTags; final List? allTags; final String spaceName; + final bool isCreate; final Function(List, List?)? onSave; const AddDeviceTypeWidget( {super.key, + required this.isCreate, this.products, this.initialSelectedProducts, this.onProductsSelected, @@ -74,7 +75,9 @@ class AddDeviceTypeWidget extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 20.0), child: ScrollableGridViewWidget( + initialProductCounts: state.selectedProducts, products: products, + isCreate: isCreate, crossAxisCount: crossAxisCount), ), ), @@ -124,17 +127,14 @@ class AddDeviceTypeWidget extends StatelessWidget { barrierDismissible: false, context: context, builder: (context) => AssignTagDialog( - products: products, - subspaces: subspaces, - addedProducts: state.selectedProducts, - allTags: allTags, - spaceName: spaceName, - initialTags: initialTags, - title: dialogTitle, - onSave: (tags, subspaces) { - onSave!(tags, subspaces); - }, - ), + products: products, + subspaces: subspaces, + addedProducts: state.selectedProducts, + allTags: allTags, + spaceName: spaceName, + initialTags: initialTags, + title: dialogTitle, + onSave: onSave), ); } }, @@ -148,29 +148,4 @@ class AddDeviceTypeWidget extends StatelessWidget { ), )); } - - List generateInitialTags({ - List? spaceTags, - List? subspaces, - }) { - final List initialTags = []; - - if (spaceTags != null) { - initialTags.addAll(spaceTags); - } - - if (subspaces != null) { - for (var subspace in subspaces) { - if (subspace.tags != null) { - initialTags.addAll( - subspace.tags!.map( - (tag) => tag.copyWith(location: subspace.subspaceName), - ), - ); - } - } - } - - return initialTags; - } } diff --git a/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart b/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart index 08ad79ac..db2d6014 100644 --- a/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart +++ b/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart @@ -13,12 +13,13 @@ import 'package:syncrow_web/utils/constants/assets.dart'; class DeviceTypeTileWidget extends StatelessWidget { final ProductModel product; final List productCounts; + final bool isCreate; - const DeviceTypeTileWidget({ - super.key, - required this.product, - required this.productCounts, - }); + const DeviceTypeTileWidget( + {super.key, + required this.product, + required this.productCounts, + required this.isCreate}); @override Widget build(BuildContext context) { @@ -48,7 +49,7 @@ class DeviceTypeTileWidget extends StatelessWidget { DeviceNameWidget(name: product.name), const SizedBox(height: 4), CounterWidget( - isCreate: false, + isCreate: isCreate, initialCount: selectedProduct.count, onCountChanged: (newCount) { context.read().add( diff --git a/lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart b/lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart index 97bcf6d1..4056744e 100644 --- a/lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart +++ b/lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart @@ -10,13 +10,14 @@ class ScrollableGridViewWidget extends StatelessWidget { final List? products; final int crossAxisCount; final List? initialProductCounts; + final bool isCreate; - const ScrollableGridViewWidget({ - super.key, - required this.products, - required this.crossAxisCount, - this.initialProductCounts, - }); + const ScrollableGridViewWidget( + {super.key, + required this.products, + required this.crossAxisCount, + this.initialProductCounts, + required this.isCreate}); @override Widget build(BuildContext context) { @@ -46,6 +47,7 @@ class ScrollableGridViewWidget extends StatelessWidget { return DeviceTypeTileWidget( product: product, + isCreate: isCreate, productCounts: initialProductCount != null ? [...productCounts, initialProductCount] : productCounts, From 9331193e901036d3264eaf8e056762a706b4b3cc Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 29 Jan 2025 09:55:36 +0400 Subject: [PATCH 142/175] added tags and subspace to update space --- .../bloc/space_management_bloc.dart | 158 ++++++++++++++++-- .../widgets/dialogs/create_space_dialog.dart | 75 ++++++--- .../create_subspace/bloc/subspace_bloc.dart | 1 - lib/services/space_mana_api.dart | 8 +- 4 files changed, 205 insertions(+), 37 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index ff584f52..31491e28 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -5,12 +5,15 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_mod import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/services/product_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; +import 'package:syncrow_web/utils/constants/action_enum.dart'; class SpaceManagementBloc extends Bloc { @@ -341,7 +344,7 @@ class SpaceManagementBloc products: _cachedProducts ?? [], selectedCommunity: selectedCommunity, selectedSpace: selectedSpace, - spaceModels: spaceModels ?? [])); + spaceModels: spaceModels)); } } catch (e) { emit(SpaceManagementError('Error updating state: $e')); @@ -428,16 +431,72 @@ class SpaceManagementBloc for (var space in orderedSpaces) { try { if (space.uuid != null && space.uuid!.isNotEmpty) { - final response = await _api.updateSpace( - communityId: communityUuid, - spaceId: space.uuid!, - name: space.name, - parentId: space.parent?.uuid, - isPrivate: space.isPrivate, - position: space.position, - icon: space.icon, - direction: space.incomingConnection?.direction, - ); + final prevSpace = await _api.getSpace(communityUuid, space.uuid!); + final List subspaceUpdates = []; + final List? prevSubspaces = prevSpace?.subspaces; + final List? newSubspaces = space.subspaces; + + if (prevSubspaces != null || newSubspaces != null) { + if (prevSubspaces != null && newSubspaces != null) { + for (var prevSubspace in prevSubspaces) { + final existsInNew = newSubspaces + .any((subspace) => subspace.uuid == prevSubspace.uuid); + if (!existsInNew) { + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.delete, uuid: prevSubspace.uuid)); + } + } + } else if (prevSubspaces != null && newSubspaces == null) { + for (var prevSubspace in prevSubspaces) { + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.delete, uuid: prevSubspace.uuid)); + } + } + + if (newSubspaces != null) { + for (var newSubspace in newSubspaces) { + // Tag without UUID + if ((newSubspace.uuid == null || newSubspace.uuid!.isEmpty)) { + final List tagUpdates = []; + + if (newSubspace.tags != null) { + for (var tag in newSubspace.tags!) { + tagUpdates.add(TagModelUpdate( + action: Action.add, + uuid: tag.uuid == '' ? null : tag.uuid, + tag: tag.tag, + productUuid: tag.product?.uuid)); + } + } + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.add, + subspaceName: newSubspace.subspaceName, + tags: tagUpdates)); + } + } + } + + if (prevSubspaces != null && newSubspaces != null) { + final newSubspaceMap = { + for (var subspace in newSubspaces) subspace.uuid: subspace + }; + + for (var prevSubspace in prevSubspaces) { + final newSubspace = newSubspaceMap[prevSubspace.uuid]; + + if (newSubspace != null) { + final List tagSubspaceUpdates = + processTagUpdates(prevSubspace.tags, newSubspace.tags); + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.update, + uuid: newSubspace.uuid, + subspaceName: newSubspace.subspaceName, + tags: tagSubspaceUpdates)); + } + } + } + } + } else { // Call create if the space does not have a UUID final List tagBodyModels = space.tags != null @@ -455,6 +514,7 @@ class SpaceManagementBloc }).toList() ?? []; + final response = await _api.createSpace( communityId: communityUuid, name: space.name, @@ -535,4 +595,80 @@ class SpaceManagementBloc emit(SpaceManagementError('Error loading communities and spaces: $e')); } } + + List processTagUpdates( + List? prevTags, + List? newTags, + ) { + final List tagUpdates = []; + final processedTags = {}; + + if (prevTags == null && newTags != null) { + for (var newTag in newTags) { + tagUpdates.add(TagModelUpdate( + action: Action.add, + tag: newTag.tag, + uuid: newTag.uuid, + productUuid: newTag.product?.uuid, + )); + } + return tagUpdates; + } + + if (newTags != null || prevTags != null) { + // Case 1: Tags deleted + if (prevTags != null && newTags != null) { + for (var prevTag in prevTags) { + final existsInNew = + newTags.any((newTag) => newTag.uuid == prevTag.uuid); + if (!existsInNew) { + tagUpdates + .add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid)); + } + } + } else if (prevTags != null && newTags == null) { + for (var prevTag in prevTags) { + tagUpdates + .add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid)); + } + } + + // Case 2: Tags added + if (newTags != null) { + final prevTagUuids = prevTags?.map((t) => t.uuid).toSet() ?? {}; + + for (var newTag in newTags) { + // Tag without UUID + if ((newTag.uuid == null || !prevTagUuids.contains(newTag.uuid)) && + !processedTags.contains(newTag.tag)) { + tagUpdates.add(TagModelUpdate( + action: Action.add, + tag: newTag.tag, + uuid: newTag.uuid == '' ? null : newTag.uuid, + productUuid: newTag.product?.uuid)); + processedTags.add(newTag.tag); + } + } + } + + // Case 3: Tags updated + if (prevTags != null && newTags != null) { + final newTagMap = {for (var tag in newTags) tag.uuid: tag}; + + for (var prevTag in prevTags) { + final newTag = newTagMap[prevTag.uuid]; + if (newTag != null) { + tagUpdates.add(TagModelUpdate( + action: Action.update, + uuid: newTag.uuid, + tag: newTag.tag, + )); + } else {} + } + } + } + + return tagUpdates; + } + } diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 50d73fff..94753107 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -79,6 +79,8 @@ class CreateSpaceDialogState extends State { widget.selectedProducts.isNotEmpty ? widget.selectedProducts : []; isOkButtonEnabled = enteredName.isNotEmpty || nameController.text.isNotEmpty; + tags = widget.tags ?? []; + subspaces = widget.subspaces ?? []; } @override @@ -237,7 +239,7 @@ class CreateSpaceDialogState extends State { ), ) : Container( - width: screenWidth * 0.35, + width: screenWidth * 0.25, padding: const EdgeInsets.symmetric( vertical: 10.0, horizontal: 16.0), decoration: BoxDecoration( @@ -312,7 +314,7 @@ class CreateSpaceDialogState extends State { ], ), const SizedBox(height: 25), - subspaces == null + subspaces == null || subspaces!.isEmpty ? TextButton( style: TextButton.styleFrom( padding: EdgeInsets.zero, @@ -344,21 +346,29 @@ class CreateSpaceDialogState extends State { runSpacing: 8.0, children: [ if (subspaces != null) - ...subspaces!.map((subspace) => - SubspaceNameDisplayWidget( - validateName: (updatedName) { - return !subspaces!.any((s) => - s != subspace && - s.subspaceName == updatedName); - }, - text: subspace.subspaceName, - onNameChanged: (updatedName) { - setState(() { - subspace.subspaceName = - updatedName; - }); - }, - )), + ...subspaces!.map((subspace) { + return Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + SubspaceNameDisplayWidget( + text: subspace.subspaceName, + validateName: (updatedName) { + return subspaces!.any((s) => + s != subspace && + s.subspaceName == + updatedName); + }, + onNameChanged: (updatedName) { + setState(() { + subspace.subspaceName = + updatedName; + }); + }, + ), + ], + ); + }), EditChip( onTap: () async { _showSubSpaceDialog(context, enteredName, @@ -425,14 +435,29 @@ class CreateSpaceDialogState extends State { ), EditChip(onTap: () async { - _showTagCreateDialog( - context, - enteredName, - widget.isEdit, - widget.products, - subspaces, + final result = await showDialog( + context: context, + builder: (context) => AssignTagDialog( + products: widget.products, + subspaces: widget.subspaces, + addedProducts: TagHelper + .createInitialSelectedProductsForTags( + tags ?? [], subspaces), + title: 'Edit Device', + initialTags: + TagHelper.generateInitialForTags( + spaceTags: tags, + subspaces: subspaces), + spaceName: widget.name ?? '', + onSave: + (updatedTags, updatedSubspaces) { + setState(() { + tags = updatedTags; + subspaces = updatedSubspaces; + }); + }, + ), ); - // Edit action }) ], ), @@ -547,6 +572,7 @@ class CreateSpaceDialogState extends State { setState(() { selectedSpaceModel = selectedModel; subspaces = null; + tags = null; }); } }, @@ -610,6 +636,7 @@ class CreateSpaceDialogState extends State { products: products, subspaces: subspaces, spaceTags: tags, + isCreate: true, allTags: [], initialSelectedProducts: TagHelper.createInitialSelectedProductsForTags( diff --git a/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart b/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart index 1a1884e2..b334a301 100644 --- a/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart +++ b/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart @@ -82,7 +82,6 @@ class SubSpaceBloc extends Bloc { )); }); - on((event, emit) { final updatedSubSpaces = state.subSpaces.map((subSpace) { if (subSpace.uuid == event.updatedSubSpace.uuid) { diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 2a2d42ad..9e6fafba 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -4,7 +4,9 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subs import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_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'; @@ -219,6 +221,8 @@ class CommunitySpaceManagementApi { String? direction, bool isPrivate = false, required Offset position, + List? tags, + List? subspaces, }) async { try { final body = { @@ -228,11 +232,13 @@ class CommunitySpaceManagementApi { 'y': position.dy, 'direction': direction, 'icon': icon, + 'subspace':subspaces, + 'tags':tags, }; if (parentId != null) { body['parentUuid'] = parentId; } - + final response = await HTTPService().put( path: ApiEndpoints.updateSpace .replaceAll('{communityId}', communityId) From 8870467fe4d574ed7425176c59780f58f6ca120a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 29 Jan 2025 09:56:10 +0400 Subject: [PATCH 143/175] fixed the edit flow of space --- .../widgets/community_structure_widget.dart | 4 +++ .../assign_tag/views/assign_tag_dialog.dart | 35 +++++++++++++------ .../views/assign_tag_models_dialog.dart | 2 +- .../views/create_subspace_model_dialog.dart | 22 +++++++----- .../widgets/subspace_model_create_widget.dart | 1 + 5 files changed, 43 insertions(+), 21 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 44122a06..a9bc6fb8 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -195,6 +195,7 @@ class _CommunityStructureAreaState extends State { screenSize, position: spaces[index].position + newPosition, + parentIndex: index, direction: direction, ); @@ -305,6 +306,7 @@ class _CommunityStructureAreaState extends State { // Set the first space in the center or use passed position Offset centerPosition = position ?? _getCenterPosition(screenSize); + print(tags); SpaceModel newSpace = SpaceModel( name: name, icon: icon, @@ -350,6 +352,8 @@ class _CommunityStructureAreaState extends State { name: widget.selectedSpace!.name, icon: widget.selectedSpace!.icon, editSpace: widget.selectedSpace, + tags: widget.selectedSpace?.tags, + subspaces: widget.selectedSpace?.subspaces, isEdit: true, onCreateSpace: (String name, String icon, diff --git a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart index 7ec19a45..bfb56d7a 100644 --- a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart +++ b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart @@ -12,6 +12,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart'; +import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class AssignTagDialog extends StatelessWidget { @@ -40,8 +41,11 @@ class AssignTagDialog extends StatelessWidget { @override Widget build(BuildContext context) { - final List locations = - (subspaces ?? []).map((subspace) => subspace.subspaceName).toList(); + final List locations = (subspaces ?? []) + .map((subspace) => subspace.subspaceName) + .toList() + ..add('Main Space'); + return BlocProvider( create: (_) => AssignTagBloc() ..add(InitializeTags( @@ -235,16 +239,18 @@ class AssignTagDialog extends StatelessWidget { Navigator.of(context).pop(); - await showDialog( - barrierDismissible: false, + await showDialog( context: context, - builder: (dialogContext) => AddDeviceTypeWidget( + builder: (context) => AddDeviceTypeWidget( products: products, subspaces: processedSubspaces, - initialSelectedProducts: addedProducts, - allTags: allTags, + initialSelectedProducts: TagHelper + .createInitialSelectedProductsForTags( + processedTags, processedSubspaces), spaceName: spaceName, spaceTags: processedTags, + isCreate: false, + onSave: onSave, ), ); }, @@ -261,7 +267,6 @@ class AssignTagDialog extends StatelessWidget { foregroundColor: ColorsManager.whiteColors, onPressed: state.isSaveEnabled ? () async { - Navigator.of(context).pop(); final updatedTags = List.from(state.tags); final result = processTags(updatedTags, subspaces); @@ -270,8 +275,8 @@ class AssignTagDialog extends StatelessWidget { result['updatedTags'] as List; final processedSubspaces = result['subspaces'] as List; - - onSave!(processedTags, processedSubspaces); + onSave?.call(processedTags, processedSubspaces); + Navigator.of(context).pop(); } : null, child: const Text('Save'), @@ -307,7 +312,15 @@ class AssignTagDialog extends StatelessWidget { final modifiedTags = List.from(updatedTags); final modifiedSubspaces = List.from(subspaces ?? []); - for (var tag in modifiedTags.toList()) { + if (subspaces != null) { + for (var subspace in subspaces) { + subspace.tags?.removeWhere( + (tag) => !modifiedTags + .any((updatedTag) => updatedTag.internalId == tag.internalId), + ); + } + } + for (var tag in modifiedTags.toList()) { if (modifiedSubspaces.isEmpty) continue; final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces); diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 90843aa2..ebec47e5 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -268,7 +268,7 @@ class AssignTagModelsDialog extends StatelessWidget { builder: (dialogContext) => AddDeviceTypeModelWidget( products: products, - subspaces: subspaces, + subspaces: processedSubspaces, isCreate: false, initialSelectedProducts: TagHelper .createInitialSelectedProducts( diff --git a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart index 19babff9..09c51f16 100644 --- a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart @@ -193,17 +193,21 @@ class CreateSubSpaceDialog extends StatelessWidget { const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: () async { - final subSpaces = context - .read() - .state - .subSpaces; - onSave!(subSpaces); - Navigator.of(context).pop(); - }, + onPressed: (state.errorMessage.isNotEmpty) + ? null + : () async { + final subSpaces = context + .read() + .state + .subSpaces; + onSave!(subSpaces); + Navigator.of(context).pop(); + }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, - foregroundColor: ColorsManager.whiteColors, + foregroundColor: state.errorMessage.isNotEmpty + ? ColorsManager.whiteColorsWithOpacity + : ColorsManager.whiteColors, child: const Text('OK'), ), ), diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 5fddfe6e..3e13f9c5 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -105,6 +105,7 @@ class _SubspaceModelCreateState extends State { isEdit: true, dialogTitle: dialogTitle, existingSubSpaces: _subspaces, + onUpdate: (subspaceModels) { setState(() { _subspaces = subspaceModels; From 073916d4ac5dde73ffa394cc79cfaffe463e322d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 29 Jan 2025 10:21:06 +0400 Subject: [PATCH 144/175] updated text theme --- .../widgets/dialogs/create_space_dialog.dart | 43 ++++++----- .../all_spaces/widgets/space_widget.dart | 6 +- .../assign_tag/views/assign_tag_dialog.dart | 34 +++++---- .../views/assign_tag_models_dialog.dart | 34 ++++----- .../view/create_community_dialog.dart | 4 +- .../views/create_subspace_model_dialog.dart | 71 ++++++++--------- .../views/create_subspace_model_dialog.dart | 76 ++++++++++--------- .../space_model/view/space_model_page.dart | 5 +- .../widgets/dynamic_room_widget.dart | 2 +- 9 files changed, 145 insertions(+), 130 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 94753107..ae4349ec 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -173,14 +173,13 @@ class CreateSpaceDialogState extends State { } }); }, - style: const TextStyle(color: Colors.black), + style: Theme.of(context).textTheme.bodyMedium, decoration: InputDecoration( hintText: 'Please enter the name', - hintStyle: const TextStyle( - fontSize: 14, - color: ColorsManager.lightGrayColor, - fontWeight: FontWeight.w400, - ), + hintStyle: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: ColorsManager.lightGrayColor), filled: true, fillColor: ColorsManager.boxColor, enabledBorder: OutlineInputBorder( @@ -253,8 +252,11 @@ class CreateSpaceDialogState extends State { Chip( label: Text( selectedSpaceModel?.modelName ?? '', - style: const TextStyle( - color: ColorsManager.spaceColor), + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: ColorsManager.spaceColor), ), backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( @@ -287,25 +289,25 @@ class CreateSpaceDialogState extends State { ), ), const SizedBox(height: 25), - const Row( + Row( children: [ - Expanded( + const Expanded( child: Divider( color: ColorsManager.neutralGray, thickness: 1.0, ), ), Padding( - padding: EdgeInsets.symmetric(horizontal: 6.0), + padding: const EdgeInsets.symmetric(horizontal: 6.0), child: Text( 'OR', - style: TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - ), + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.bold), ), ), - Expanded( + const Expanded( child: Divider( color: ColorsManager.neutralGray, thickness: 1.0, @@ -418,9 +420,12 @@ class CreateSpaceDialogState extends State { ), label: Text( 'x${entry.value}', // Show count - style: const TextStyle( - color: ColorsManager.spaceColor, - ), + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: ColorsManager + .spaceColor), ), backgroundColor: ColorsManager.whiteColors, diff --git a/lib/pages/spaces_management/all_spaces/widgets/space_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/space_widget.dart index 6e1f50c1..62d8197c 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/space_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/space_widget.dart @@ -20,8 +20,7 @@ class SpaceWidget extends StatelessWidget { top: position.dy, child: GestureDetector( onTap: onTap, - child: - Container( + child: Container( padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( color: ColorsManager.whiteColors, @@ -39,11 +38,10 @@ class SpaceWidget extends StatelessWidget { children: [ const Icon(Icons.location_on, color: ColorsManager.spaceColor), const SizedBox(width: 8), - Text(name, style: const TextStyle(fontSize: 16)), + Text(name, style: Theme.of(context).textTheme.bodyMedium), ], ), ), - ), ); } diff --git a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart index bfb56d7a..cb1f7b46 100644 --- a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart +++ b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart @@ -97,21 +97,22 @@ class AssignTagDialog extends StatelessWidget { ], rows: state.tags.isEmpty ? [ - const DataRow(cells: [ + DataRow(cells: [ DataCell( Center( - child: Text( - 'No Data Available', - style: TextStyle( - fontSize: 14, - color: ColorsManager.lightGrayColor, - ), - ), + child: Text('No Data Available', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + color: ColorsManager + .lightGrayColor, + )), ), ), - DataCell(SizedBox()), - DataCell(SizedBox()), - DataCell(SizedBox()), + const DataCell(SizedBox()), + const DataCell(SizedBox()), + const DataCell(SizedBox()), ]) ] : List.generate(state.tags.length, (index) { @@ -213,10 +214,11 @@ class AssignTagDialog extends StatelessWidget { ), ), if (state.errorMessage != null) - Text( - state.errorMessage!, - style: const TextStyle(color: ColorsManager.warningRed), - ), + Text(state.errorMessage!, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.warningRed)), ], ), ), @@ -320,7 +322,7 @@ class AssignTagDialog extends StatelessWidget { ); } } - for (var tag in modifiedTags.toList()) { + for (var tag in modifiedTags.toList()) { if (modifiedSubspaces.isEmpty) continue; final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces); diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index ebec47e5..9696723a 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -112,22 +112,22 @@ class AssignTagModelsDialog extends StatelessWidget { ], rows: state.tags.isEmpty ? [ - const DataRow(cells: [ + DataRow(cells: [ DataCell( Center( - child: Text( - 'No Data Available', - style: TextStyle( - fontSize: 14, - color: - ColorsManager.lightGrayColor, - ), - ), + child: Text('No Devices Available', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + color: ColorsManager + .lightGrayColor, + )), ), ), - DataCell(SizedBox()), - DataCell(SizedBox()), - DataCell(SizedBox()), + const DataCell(SizedBox()), + const DataCell(SizedBox()), + const DataCell(SizedBox()), ]) ] : List.generate(state.tags.length, (index) { @@ -233,11 +233,11 @@ class AssignTagModelsDialog extends StatelessWidget { ), ), if (state.errorMessage != null) - Text( - state.errorMessage!, - style: const TextStyle( - color: ColorsManager.warningRed), - ), + Text(state.errorMessage!, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.warningRed)), ], ), ), diff --git a/lib/pages/spaces_management/create_community/view/create_community_dialog.dart b/lib/pages/spaces_management/create_community/view/create_community_dialog.dart index 1a5460d1..13e676b5 100644 --- a/lib/pages/spaces_management/create_community/view/create_community_dialog.dart +++ b/lib/pages/spaces_management/create_community/view/create_community_dialog.dart @@ -77,9 +77,7 @@ class CreateCommunityDialog extends StatelessWidget { .read() .add(ValidateCommunityNameEvent(value)); }, - style: const TextStyle( - color: ColorsManager.blackColor, - ), + style: Theme.of(context).textTheme.bodyMedium, decoration: InputDecoration( hintText: 'Please enter the community name', filled: true, diff --git a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart index 09c51f16..0a2a01e5 100644 --- a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart @@ -102,12 +102,13 @@ class CreateSubSpaceDialog extends StatelessWidget { duplicateIndices.indexOf(index) != 0; return Chip( - label: Text( - subSpace.subspaceName, - style: const TextStyle( - color: ColorsManager.spaceColor, - ), - ), + label: Text(subSpace.subspaceName, + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + color: + ColorsManager.spaceColor)), backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), @@ -143,27 +144,29 @@ class CreateSubSpaceDialog extends StatelessWidget { SizedBox( width: 200, child: TextField( - controller: textController, - decoration: InputDecoration( - border: InputBorder.none, - hintText: state.subSpaces.isEmpty - ? 'Please enter the name' - : null, - hintStyle: const TextStyle( - color: ColorsManager.lightGrayColor), - ), - onSubmitted: (value) { - if (value.trim().isNotEmpty) { - context.read().add( - AddSubSpace(SubspaceModel( - subspaceName: value.trim(), - disabled: false))); - textController.clear(); - } - }, - style: const TextStyle( - color: ColorsManager.blackColor), - ), + 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( + AddSubSpace(SubspaceModel( + subspaceName: value.trim(), + disabled: false))); + textController.clear(); + } + }, + style: + Theme.of(context).textTheme.bodyMedium), ), ], ), @@ -171,13 +174,13 @@ class CreateSubSpaceDialog extends StatelessWidget { if (state.errorMessage.isNotEmpty) Padding( padding: const EdgeInsets.only(top: 8.0), - child: Text( - state.errorMessage, - style: const TextStyle( - color: ColorsManager.warningRed, - fontSize: 12, - ), - ), + child: Text(state.errorMessage, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: ColorsManager.warningRed, + )), ), const SizedBox(height: 16), Row( 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 4c0cb99f..7a39891b 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 @@ -94,12 +94,13 @@ class CreateSubSpaceModelDialog extends StatelessWidget { duplicateIndices.indexOf(index) != 0; return Chip( - label: Text( - subSpace.subspaceName, - style: const TextStyle( - color: ColorsManager.spaceColor, - ), - ), + label: Text(subSpace.subspaceName, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: ColorsManager.spaceColor, + )), backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), @@ -135,28 +136,33 @@ class CreateSubSpaceModelDialog extends StatelessWidget { SizedBox( width: 200, child: TextField( - controller: textController, - decoration: InputDecoration( - border: InputBorder.none, - hintText: state.subSpaces.isEmpty - ? 'Please enter the name' - : null, - hintStyle: const TextStyle( - color: ColorsManager.lightGrayColor), - ), - onSubmitted: (value) { - if (value.trim().isNotEmpty) { - context.read().add( - AddSubSpaceModel( - SubspaceTemplateModel( - subspaceName: value.trim(), - disabled: false))); - textController.clear(); - } - }, - style: const TextStyle( - color: ColorsManager.blackColor), - ), + 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)), ), ], ), @@ -164,13 +170,13 @@ class CreateSubSpaceModelDialog extends StatelessWidget { if (state.errorMessage.isNotEmpty) Padding( padding: const EdgeInsets.only(bottom: 16.0), - child: Text( - state.errorMessage, - style: const TextStyle( - color: ColorsManager.red, - fontSize: 12, - ), - ), + child: Text(state.errorMessage, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: ColorsManager.red, + )), ), const SizedBox(height: 16), Row( diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index cb7bc0c9..ec6d54a0 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -97,7 +97,10 @@ class SpaceModelPage extends StatelessWidget { return Center( child: Text( 'Error: ${state.message}', - style: const TextStyle(color: ColorsManager.warningRed), + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.warningRed), ), ); } diff --git a/lib/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart b/lib/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart index e24c7704..f3da4122 100644 --- a/lib/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart @@ -29,7 +29,7 @@ class DynamicRoomWidget extends StatelessWidget { final TextPainter textPainter = TextPainter( text: TextSpan( text: subspace.subspaceName, - style: const TextStyle(fontSize: 16), + style: Theme.of(context).textTheme.bodyMedium ), textDirection: TextDirection.ltr, )..layout(); From 4bae7bb9fbdb7fae934b0b70fe61e80ee17154d6 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 29 Jan 2025 11:14:12 +0400 Subject: [PATCH 145/175] fixed update space --- .../bloc/space_management_bloc.dart | 20 ++++++++++++++++--- .../all_spaces/model/space_model.dart | 1 - .../widgets/community_structure_widget.dart | 1 - .../widgets/dialogs/create_space_dialog.dart | 20 ++++++++++++++++++- lib/services/space_mana_api.dart | 16 +++++++-------- 5 files changed, 44 insertions(+), 14 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 31491e28..1b5692c6 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -431,11 +431,15 @@ class SpaceManagementBloc for (var space in orderedSpaces) { try { if (space.uuid != null && space.uuid!.isNotEmpty) { + List tagUpdates = []; + final prevSpace = await _api.getSpace(communityUuid, space.uuid!); final List subspaceUpdates = []; final List? prevSubspaces = prevSpace?.subspaces; final List? newSubspaces = space.subspaces; + tagUpdates = processTagUpdates(prevSpace?.tags, space.tags); + if (prevSubspaces != null || newSubspaces != null) { if (prevSubspaces != null && newSubspaces != null) { for (var prevSubspace in prevSubspaces) { @@ -497,6 +501,18 @@ class SpaceManagementBloc } } + final response = await _api.updateSpace( + communityId: communityUuid, + spaceId: space.uuid!, + name: space.name, + parentId: space.parent?.uuid, + isPrivate: space.isPrivate, + position: space.position, + icon: space.icon, + subspaces: subspaceUpdates, + tags: tagUpdates, + direction: space.incomingConnection?.direction, + ); } else { // Call create if the space does not have a UUID final List tagBodyModels = space.tags != null @@ -514,7 +530,6 @@ class SpaceManagementBloc }).toList() ?? []; - final response = await _api.createSpace( communityId: communityUuid, name: space.name, @@ -596,7 +611,7 @@ class SpaceManagementBloc } } - List processTagUpdates( + List processTagUpdates( List? prevTags, List? newTags, ) { @@ -670,5 +685,4 @@ class SpaceManagementBloc return tagUpdates; } - } diff --git a/lib/pages/spaces_management/all_spaces/model/space_model.dart b/lib/pages/spaces_management/all_spaces/model/space_model.dart index c8da9d9e..6ad91dad 100644 --- a/lib/pages/spaces_management/all_spaces/model/space_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/space_model.dart @@ -66,7 +66,6 @@ class SpaceModel { final instance = SpaceModel( internalId: internalId, uuid: json['uuid'] ?? '', - spaceTuyaUuid: json['spaceTuyaUuid'], name: json['spaceName'], isPrivate: json['isPrivate'] ?? false, invitationCode: json['invitationCode'], diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index a9bc6fb8..7699dede 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -306,7 +306,6 @@ class _CommunityStructureAreaState extends State { // Set the first space in the center or use passed position Offset centerPosition = position ?? _getCenterPosition(screenSize); - print(tags); SpaceModel newSpace = SpaceModel( name: name, icon: icon, diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index ae4349ec..eee8a25c 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -629,7 +629,25 @@ class CreateSpaceDialogState extends State { products: products, subspaces: subspaces, allTags: [], - onSave: (selectedSpaceTags, selectedSubspaces) {}, + onSave: (selectedSpaceTags, selectedSubspaces) { + setState(() { + tags = selectedSpaceTags; + selectedSpaceModel = null; + + if (selectedSubspaces != null) { + if (subspaces != null) { + for (final subspace in subspaces!) { + for (final selectedSubspace in selectedSubspaces) { + if (subspace.subspaceName == + selectedSubspace.subspaceName) { + subspace.tags = selectedSubspace.tags; + } + } + } + } + } + }); + }, ); }, ) diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 9e6fafba..c4877c98 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -156,7 +156,7 @@ class CommunitySpaceManagementApi { .replaceAll('{spaceId}', spaceId) .replaceAll('{projectId}', TempConst.projectId), expectedResponseModel: (json) { - return SpaceModel.fromJson(json); + return SpaceModel.fromJson(json['data']); }, ); return response; @@ -212,7 +212,7 @@ class CommunitySpaceManagementApi { } } - Future updateSpace({ + Future updateSpace({ required String communityId, required spaceId, required String name, @@ -232,13 +232,13 @@ class CommunitySpaceManagementApi { 'y': position.dy, 'direction': direction, 'icon': icon, - 'subspace':subspaces, - 'tags':tags, + 'subspace': subspaces, + 'tags': tags, }; if (parentId != null) { body['parentUuid'] = parentId; } - + final response = await HTTPService().put( path: ApiEndpoints.updateSpace .replaceAll('{communityId}', communityId) @@ -246,13 +246,13 @@ class CommunitySpaceManagementApi { .replaceAll('{projectId}', TempConst.projectId), body: body, expectedResponseModel: (json) { - return SpaceModel.fromJson(json['data']); + return json['success'] ?? false; }, ); return response; } catch (e) { - debugPrint('Error creating space: $e'); - return null; + debugPrint('Error updating space: $e'); + return false; } } From e4262d08a565b39b6d06e55c3bf6c397c77dd54f Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 29 Jan 2025 11:40:53 +0400 Subject: [PATCH 146/175] fixed ok button --- .../widgets/dialog/create_space_model_dialog.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 2fb2a5b0..212400b9 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -167,7 +167,8 @@ class CreateSpaceModelDialog extends StatelessWidget { onPressed: ((state.errorMessage != null && state.errorMessage != '') || !isNameValid) - ? () { + ? null + : () { final updatedSpaceTemplate = updatedSpaceModel.copyWith( modelName: @@ -240,8 +241,7 @@ class CreateSpaceModelDialog extends StatelessWidget { } } } - } - : null, + }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, foregroundColor: ((state.errorMessage != null && From c173df934d5bb4de29189df838acc2aafe4f6d24 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 29 Jan 2025 12:24:12 +0400 Subject: [PATCH 147/175] added all tags --- .../widgets/community_structure_widget.dart | 12 ++ .../widgets/dialogs/create_space_dialog.dart | 6 +- .../space_model/view/space_model_page.dart | 8 +- .../widgets/space_model_card_widget.dart | 148 +++++++++--------- 4 files changed, 98 insertions(+), 76 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 7699dede..3a208a0d 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -295,6 +295,7 @@ class _CommunityStructureAreaState extends State { return CreateSpaceDialog( products: widget.products, spaceModels: widget.spaceModels, + allTags: _getAllTagValues(spaces), parentSpace: parentIndex != null ? spaces[parentIndex] : null, onCreateSpace: (String name, String icon, @@ -354,6 +355,7 @@ class _CommunityStructureAreaState extends State { tags: widget.selectedSpace?.tags, subspaces: widget.selectedSpace?.subspaces, isEdit: true, + allTags: _getAllTagValues(spaces), onCreateSpace: (String name, String icon, List selectedProducts, @@ -745,4 +747,14 @@ class _CommunityStructureAreaState extends State { duplicateRecursive(space, space.position, duplicatedParent); } } + + List _getAllTagValues(List spaces) { + final List allTags = []; + for (final space in spaces) { + if (space.tags != null) { + allTags.addAll(space.listAllTagValues()); + } + } + return allTags; + } } diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index eee8a25c..405ccd92 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -39,6 +39,7 @@ class CreateSpaceDialog extends StatefulWidget { final List? spaceModels; final List? subspaces; final List? tags; + final List? allTags; const CreateSpaceDialog( {super.key, @@ -49,6 +50,7 @@ class CreateSpaceDialog extends StatefulWidget { this.icon, this.isEdit = false, this.editSpace, + this.allTags, this.selectedProducts = const [], this.spaceModels, this.subspaces, @@ -628,7 +630,7 @@ class CreateSpaceDialogState extends State { spaceName: name, products: products, subspaces: subspaces, - allTags: [], + allTags: widget.allTags, onSave: (selectedSpaceTags, selectedSubspaces) { setState(() { tags = selectedSpaceTags; @@ -660,7 +662,7 @@ class CreateSpaceDialogState extends State { subspaces: subspaces, spaceTags: tags, isCreate: true, - allTags: [], + allTags: widget.allTags, initialSelectedProducts: TagHelper.createInitialSelectedProductsForTags( tags, subspaces), diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index ec6d54a0..b1fce7a1 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -112,14 +112,14 @@ class SpaceModelPage extends StatelessWidget { double _calculateChildAspectRatio(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; if (screenWidth > 1600) { - return 2; + return 1.5; // Decrease to make cards taller } if (screenWidth > 1200) { - return 3; + return 2.0; } else if (screenWidth > 800) { - return 3.5; + return 2.5; } else { - return 4.0; + return 3.0; } } 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 df0fba4f..0056c96f 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 @@ -31,82 +31,90 @@ class SpaceModelCardWidget extends StatelessWidget { } } - return Container( - padding: const EdgeInsets.all(16.0), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(10), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: const Offset(0, 3), + return LayoutBuilder( + builder: (context, constraints) { + bool showOnlyName = constraints.maxWidth < 250; + return Container( + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: const Offset(0, 3), + ), + ], ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - model.modelName, - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - color: Colors.black, - fontWeight: FontWeight.bold, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 10), - Expanded( - child: Row( - children: [ - // Left Container + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + model.modelName, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: Colors.black, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (!showOnlyName) ...[ + const SizedBox(height: 10), Expanded( - flex: 1, // Distribute space proportionally - child: Container( - padding: const EdgeInsets.all(8.0), - child: LayoutBuilder( - builder: (context, constraints) { - return Align( - alignment: Alignment.topLeft, - child: DynamicRoomWidget( - subspaceModels: model.subspaceModels, - maxWidth: constraints.maxWidth, - maxHeight: constraints.maxHeight, + child: Row( + children: [ + // Left Container + Expanded( + flex: 1, // Distribute space proportionally + child: Container( + padding: const EdgeInsets.all(8.0), + child: LayoutBuilder( + builder: (context, constraints) { + return Align( + alignment: Alignment.topLeft, + child: DynamicRoomWidget( + subspaceModels: model.subspaceModels, + maxWidth: constraints.maxWidth, + maxHeight: constraints.maxHeight, + ), + ); + }, ), - ); - }, - ), + ), + ), + if (productTagCount.isNotEmpty && + model.subspaceModels != null) + Container( + width: 1.0, + color: ColorsManager.softGray, + margin: const EdgeInsets.symmetric(vertical: 6.0), + ), + Expanded( + flex: 1, // Distribute space proportionally + child: Container( + padding: const EdgeInsets.all(8.0), + child: LayoutBuilder( + builder: (context, constraints) { + return Align( + alignment: Alignment.topLeft, + child: DynamicProductWidget( + productTagCount: productTagCount, + maxWidth: constraints.maxWidth, + maxHeight: constraints.maxHeight)); + }, + ), + ), + ), + ], ), ), - if (productTagCount.isNotEmpty && model.subspaceModels != null) - Container( - width: 1.0, - color: ColorsManager.softGray, - margin: const EdgeInsets.symmetric(vertical: 6.0), - ), - Expanded( - flex: 1, // Distribute space proportionally - child: Container( - padding: const EdgeInsets.all(8.0), - child: LayoutBuilder( - builder: (context, constraints) { - return Align( - alignment: Alignment.topLeft, - child: DynamicProductWidget( - productTagCount: productTagCount, - maxWidth: constraints.maxWidth, - maxHeight: constraints.maxHeight)); - }, - ), - ), - ), - ], - ), + ] + ], ), - ], - ), + ); + }, ); } } From 09c1a785e5f3dd3b5c7481eeb21a1dc261c34d40 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 29 Jan 2025 22:03:56 +0400 Subject: [PATCH 148/175] add subspace validation --- .../all_spaces/model/subspace_model.dart | 12 +++++++++--- .../widgets/dialogs/create_space_dialog.dart | 19 +++++++++++++++---- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/model/subspace_model.dart b/lib/pages/spaces_management/all_spaces/model/subspace_model.dart index 2c86523f..a89ec409 100644 --- a/lib/pages/spaces_management/all_spaces/model/subspace_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/subspace_model.dart @@ -1,5 +1,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart'; +import 'package:uuid/uuid.dart'; import 'tag.dart'; @@ -8,19 +9,24 @@ class SubspaceModel { String subspaceName; final bool disabled; List? tags; + String internalId; SubspaceModel({ this.uuid, required this.subspaceName, required this.disabled, this.tags, - }); + String? internalId, + }) : internalId = internalId ?? const Uuid().v4(); factory SubspaceModel.fromJson(Map json) { + final String internalId = json['internalId'] ?? const Uuid().v4(); + return SubspaceModel( uuid: json['uuid'] ?? '', subspaceName: json['subspaceName'] ?? '', disabled: json['disabled'] ?? false, + internalId: internalId, tags: (json['tags'] as List?) ?.map((item) => Tag.fromJson(item)) .toList() ?? @@ -43,7 +49,7 @@ class UpdateSubspaceModel { final Action action; final String? subspaceName; final List? tags; - UpdateSubspaceModel({ + UpdateSubspaceModel({ required this.action, required this.uuid, this.subspaceName, @@ -107,4 +113,4 @@ class UpdateTag { 'product': product?.toMap(), }; } -} \ No newline at end of file +} diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 405ccd92..ada60850 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -358,10 +358,21 @@ class CreateSpaceDialogState extends State { SubspaceNameDisplayWidget( text: subspace.subspaceName, validateName: (updatedName) { - return subspaces!.any((s) => - s != subspace && - s.subspaceName == - updatedName); + bool nameExists = + subspaces!.any((s) { + bool isSameId = s.internalId == + subspace.internalId; + bool isSameName = s.subspaceName + .trim() + .toLowerCase() == + updatedName + .trim() + .toLowerCase(); + + return !isSameId && isSameName; + }); + + return !nameExists; }, onNameChanged: (updatedName) { setState(() { From 43c17d1c18ecb4e4b4c3bf6282a297370ff4d275 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Thu, 30 Jan 2025 04:03:54 +0300 Subject: [PATCH 149/175] Implemented the selection behavior of the side tree --- lib/common/widgets/search_bar.dart | 15 +- .../widgets/device_managment_body.dart | 15 +- lib/pages/home/view/home_page_web.dart | 2 +- lib/pages/routines/view/routines_view.dart | 19 +- .../space_tree/bloc/space_tree_bloc.dart | 180 +++++++++++-- .../space_tree/bloc/space_tree_event.dart | 60 ++++- .../space_tree/bloc/space_tree_state.dart | 52 +++- lib/pages/space_tree/view/community_tile.dart | 35 --- .../space_tree/view/custom_expansion.dart | 118 ++++---- .../space_tree/view/side_spaces_view.dart | 26 -- lib/pages/space_tree/view/side_tree_view.dart | 120 +++++++++ lib/pages/space_tree/view/space_tile.dart | 52 ---- .../space_tree/view/space_tree_view.dart | 251 ------------------ lib/services/routines_api.dart | 2 +- 14 files changed, 446 insertions(+), 501 deletions(-) delete mode 100644 lib/pages/space_tree/view/community_tile.dart delete mode 100644 lib/pages/space_tree/view/side_spaces_view.dart create mode 100644 lib/pages/space_tree/view/side_tree_view.dart delete mode 100644 lib/pages/space_tree/view/space_tile.dart delete mode 100644 lib/pages/space_tree/view/space_tree_view.dart diff --git a/lib/common/widgets/search_bar.dart b/lib/common/widgets/search_bar.dart index 728fad33..9d17d1c1 100644 --- a/lib/common/widgets/search_bar.dart +++ b/lib/common/widgets/search_bar.dart @@ -46,14 +46,13 @@ class CustomSearchBar extends StatelessWidget { filled: true, fillColor: ColorsManager.textFieldGreyColor, hintText: hintText, - hintStyle: TextStyle( - color: Color(0xB2999999), - fontSize: 12, - fontFamily: 'Aftika', - fontWeight: FontWeight.w400, - height: 0, - letterSpacing: -0.24, - ), + hintStyle: Theme.of(context).textTheme.bodyLarge!.copyWith( + color: ColorsManager.lightGrayColor, + fontSize: 12, + fontWeight: FontWeight.w400, + height: 0, + letterSpacing: -0.24, + ), suffixIcon: Padding( padding: const EdgeInsets.only(right: 16), child: SvgPicture.asset( diff --git a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart index af4e8039..d97972d4 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart @@ -8,7 +8,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart'; -import 'package:syncrow_web/pages/space_tree/view/side_spaces_view.dart'; +import 'package:syncrow_web/pages/space_tree/view/side_tree_view.dart'; import 'package:syncrow_web/utils/format_date_time.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -62,12 +62,13 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { return Row( children: [ - Flexible(child: SideSpacesView( - onSelectAction: (String communityId, String spaceId) { - context.read().add(FetchDevices(communityId, spaceId)); - }, - )), - Flexible( + const Expanded( + child: SideTreeView( + // onSelectAction: (String communityId, String spaceId) { + // context.read().add(FetchDevices(communityId, spaceId)); + // }, + )), + Expanded( flex: 3, child: state is DeviceManagementLoading ? const Center(child: CircularProgressIndicator()) diff --git a/lib/pages/home/view/home_page_web.dart b/lib/pages/home/view/home_page_web.dart index 3e4ad932..2b7b5d95 100644 --- a/lib/pages/home/view/home_page_web.dart +++ b/lib/pages/home/view/home_page_web.dart @@ -82,7 +82,7 @@ class HomeWebPage extends StatelessWidget { child: GridView.builder( itemCount: 3, //8 gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, + crossAxisCount: 3, //4 crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, childAspectRatio: 1.5, diff --git a/lib/pages/routines/view/routines_view.dart b/lib/pages/routines/view/routines_view.dart index f1befb05..e7601729 100644 --- a/lib/pages/routines/view/routines_view.dart +++ b/lib/pages/routines/view/routines_view.dart @@ -5,7 +5,7 @@ import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart'; import 'package:syncrow_web/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart'; import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_view_card.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; -import 'package:syncrow_web/pages/space_tree/view/side_spaces_view.dart'; +import 'package:syncrow_web/pages/space_tree/view/side_tree_view.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; @@ -32,13 +32,16 @@ class _RoutinesViewState extends State { } return Row( children: [ - Expanded(child: SideSpacesView( - onSelectAction: (String communityId, String spaceId) { - context.read() - ..add(LoadScenes(spaceId, communityId)) - ..add(LoadAutomation(spaceId)); - }, - )), + const Expanded( + child: + // SideSpacesView( + // onSelectAction: (String communityId, String spaceId) { + // // context.read() + // // ..add(LoadScenes(spaceId, communityId)) + // // ..add(LoadAutomation(spaceId)); + // }, + // ) + SideTreeView()), Expanded( flex: 3, child: Padding( diff --git a/lib/pages/space_tree/bloc/space_tree_bloc.dart b/lib/pages/space_tree/bloc/space_tree_bloc.dart index 41b0d6b0..78da9fd2 100644 --- a/lib/pages/space_tree/bloc/space_tree_bloc.dart +++ b/lib/pages/space_tree/bloc/space_tree_bloc.dart @@ -8,21 +8,26 @@ import 'package:syncrow_web/services/space_mana_api.dart'; class SpaceTreeBloc extends Bloc { String selectedCommunityId = ''; String selectedSpaceId = ''; + SpaceTreeBloc() : super(const SpaceTreeState()) { on(_fetchSpaces); - on(_onSelectSpace); + on(_onCommunityExpanded); + on(_onSpaceExpanded); + on(_onCommunitySelected); + on(_onSpaceSelected); + on(_onSearch); } _fetchSpaces(InitialEvent event, Emitter emit) async { emit(SpaceTreeLoadingState()); try { - // _onloadProducts(); List communities = await CommunitySpaceManagementApi().fetchCommunities(); List updatedCommunities = await Future.wait( communities.map((community) async { List spaces = await CommunitySpaceManagementApi().getSpaceHierarchy(community.uuid); + return CommunityModel( uuid: community.uuid, createdAt: community.createdAt, @@ -35,35 +40,170 @@ class SpaceTreeBloc extends Bloc { }).toList(), ); - if (updatedCommunities.isNotEmpty && - state.selectedSpace.isEmpty && - state.selectedCommunity.isEmpty) { - selectedCommunityId = updatedCommunities[0].uuid; - } else { - selectedCommunityId = state.selectedCommunity; - selectedSpaceId = state.selectedSpace; - } - emit(state.copyWith( - communitiesList: updatedCommunities, - selectedCommunity: selectedCommunityId, - selectedSpace: selectedSpaceId)); + communitiesList: updatedCommunities, expandedCommunity: [], expandedSpaces: [])); } catch (e) { emit(SpaceTreeErrorState('Error loading communities and spaces: $e')); } } - _onSelectSpace(OnSelectSpaceEvent event, Emitter emit) async { + _onCommunityExpanded(OnCommunityExpanded event, Emitter emit) async { try { - selectedCommunityId = event.communityId; - selectedSpaceId = event.spaceId; + List updatedExpandedCommunityList = List.from(state.expandedCommunities); + + if (updatedExpandedCommunityList.contains(event.communityId)) { + updatedExpandedCommunityList.remove(event.communityId); + } else { + updatedExpandedCommunityList.add(event.communityId); + } emit(state.copyWith( - communitiesList: state.spacesList, - selectedCommunity: event.communityId, - selectedSpace: event.spaceId)); + expandedCommunity: updatedExpandedCommunityList, + )); } catch (e) { emit(const SpaceTreeErrorState('Something went wrong')); } } + + _onSpaceExpanded(OnSpaceExpanded event, Emitter emit) async { + try { + List updatedExpandedSpacesList = List.from(state.expandedSpaces); + + if (updatedExpandedSpacesList.contains(event.spaceId)) { + updatedExpandedSpacesList.remove(event.spaceId); + } else { + updatedExpandedSpacesList.add(event.spaceId); + } + + emit(state.copyWith(expandedSpaces: updatedExpandedSpacesList)); + } catch (e) { + emit(const SpaceTreeErrorState('Something went wrong')); + } + } + + _onCommunitySelected(OnCommunitySelected event, Emitter emit) async { + try { + List updatedSelectedCommunities = List.from(state.selectedCommunities); + List updatedSelectedSpaces = List.from(state.selectedSpaces); + List updatedSoldChecks = List.from(state.soldCheck); + + List childrenIds = _getAllChildIds(event.children); + + if (!updatedSelectedCommunities.contains(event.communityId)) { + // Select the community and all its children + updatedSelectedCommunities.add(event.communityId); + updatedSelectedSpaces.addAll(childrenIds); + } else { + // Unselect the community and all its children + updatedSelectedCommunities.remove(event.communityId); + updatedSelectedSpaces.removeWhere(childrenIds.contains); + updatedSoldChecks.removeWhere(childrenIds.contains); + } + + emit(state.copyWith( + selectedCommunities: updatedSelectedCommunities, + selectedSpaces: updatedSelectedSpaces, + soldCheck: updatedSoldChecks)); + } catch (e) { + emit(const SpaceTreeErrorState('Something went wrong')); + } + } + + _onSpaceSelected(OnSpaceSelected event, Emitter emit) async { + try { + List updatedSelectedCommunities = List.from(state.selectedCommunities); + List updatedSelectedSpaces = List.from(state.selectedSpaces); + List updatedSoldChecks = List.from(state.soldCheck); + + List childrenIds = _getAllChildIds(event.children); + + // List childrenIds = _getAllChildSpaceIds(event.communityId); + + if (!updatedSelectedSpaces.contains(event.spaceId) && + !updatedSoldChecks.contains(event.spaceId)) { + // First click: Select the space and all its children + updatedSelectedSpaces.add(event.spaceId); + updatedSelectedCommunities.add(event.communityId); + if (childrenIds.isNotEmpty) { + updatedSelectedSpaces.addAll(childrenIds); + } + } else if (!updatedSoldChecks.contains(event.spaceId) && childrenIds.isNotEmpty) { + // Second click: Unselect space but keep children + updatedSelectedSpaces.remove(event.spaceId); + updatedSoldChecks.add(event.spaceId); + } else { + // Third click: Unselect space and all its children + if (!_anySpacesSelectedInCommunity(event.communityId)) { + updatedSelectedCommunities.remove(event.communityId); + } + updatedSelectedSpaces.remove(event.spaceId); + if (childrenIds.isNotEmpty) { + updatedSelectedSpaces.removeWhere(childrenIds.contains); + } + updatedSoldChecks.remove(event.spaceId); + } + + emit(state.copyWith( + selectedCommunities: updatedSelectedCommunities, + selectedSpaces: updatedSelectedSpaces, + soldCheck: updatedSoldChecks)); + emit(state.copyWith(selectedSpaces: updatedSelectedSpaces)); + } catch (e) { + emit(const SpaceTreeErrorState('Something went wrong')); + } + } + + _onSearch(SearchQueryEvent event, Emitter emit) async { + try { + List communities = List.from(state.communityList); + List filteredCommunity = []; + + // Filter communities and expand only those that match the query + filteredCommunity = communities.where((community) { + final containsQueryInCommunity = + community.name.toLowerCase().contains(event.searchQuery.toLowerCase()); + final containsQueryInSpaces = + community.spaces.any((space) => _containsQuery(space, event.searchQuery.toLowerCase())); + + return containsQueryInCommunity || containsQueryInSpaces; + }).toList(); + + emit(state.copyWith( + filteredCommunity: filteredCommunity, isSearching: event.searchQuery.isNotEmpty)); + } catch (e) { + emit(const SpaceTreeErrorState('Something went wrong')); + } + } + + // Helper function to determine if any space or its children match the search query + bool _containsQuery(SpaceModel space, String query) { + final matchesSpace = space.name.toLowerCase().contains(query); + final matchesChildren = + space.children.any((child) => _containsQuery(child, query)); // Recursive check for children + + return matchesSpace || matchesChildren; + } + + List _getAllChildIds(List spaces) { + List ids = []; + for (var child in spaces) { + ids.add(child.uuid!); + ids.addAll(_getAllChildIds(child.children)); + } + return ids; + } + + bool _anySpacesSelectedInCommunity(String communityId) { + bool result = false; + for (var community in state.communityList) { + if (community.uuid == communityId) { + List ids = _getAllChildIds(community.spaces); + for (var id in ids) { + result = state.selectedSpaces.contains(id); + break; + } + } + } + return result; + } } diff --git a/lib/pages/space_tree/bloc/space_tree_event.dart b/lib/pages/space_tree/bloc/space_tree_event.dart index d6c95579..7b1b550c 100644 --- a/lib/pages/space_tree/bloc/space_tree_event.dart +++ b/lib/pages/space_tree/bloc/space_tree_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; class SpaceTreeEvent extends Equatable { const SpaceTreeEvent(); @@ -9,16 +10,6 @@ class SpaceTreeEvent extends Equatable { class InitialEvent extends SpaceTreeEvent {} -class OnSelectSpaceEvent extends SpaceTreeEvent { - final String communityId; - final String spaceId; - - const OnSelectSpaceEvent(this.communityId, this.spaceId); - - @override - List get props => [communityId, spaceId]; -} - class SearchForSpace extends SpaceTreeEvent { final String searchQuery; @@ -27,3 +18,52 @@ class SearchForSpace extends SpaceTreeEvent { @override List get props => [searchQuery]; } + +class OnCommunityExpanded extends SpaceTreeEvent { + final String communityId; + + const OnCommunityExpanded(this.communityId); + + @override + List get props => [communityId]; +} + +class OnCommunitySelected extends SpaceTreeEvent { + final String communityId; + final List children; + + const OnCommunitySelected(this.communityId, this.children); + + @override + List get props => [communityId, children]; +} + +class OnSpaceExpanded extends SpaceTreeEvent { + final String communityId; + final String spaceId; + + const OnSpaceExpanded(this.communityId, this.spaceId); + + @override + List get props => [communityId, spaceId]; +} + +class OnSpaceSelected extends SpaceTreeEvent { + final String communityId; + final String spaceId; + final List children; + + const OnSpaceSelected(this.communityId, this.spaceId, this.children); + + @override + List get props => [communityId, spaceId, children]; +} + +class SearchQueryEvent extends SpaceTreeEvent { + final String searchQuery; + + const SearchQueryEvent(this.searchQuery); + + @override + List get props => [searchQuery]; +} diff --git a/lib/pages/space_tree/bloc/space_tree_state.dart b/lib/pages/space_tree/bloc/space_tree_state.dart index ab82f2dc..b5c987ef 100644 --- a/lib/pages/space_tree/bloc/space_tree_state.dart +++ b/lib/pages/space_tree/bloc/space_tree_state.dart @@ -2,23 +2,56 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; class SpaceTreeState extends Equatable { - final List spacesList; - final String selectedSpace; - final String selectedCommunity; + final List communityList; + final List filteredCommunity; + final List expandedCommunities; + final List expandedSpaces; + final List selectedCommunities; + final List selectedSpaces; + final List soldCheck; + final bool isSearching; const SpaceTreeState( - {this.spacesList = const [], this.selectedSpace = '', this.selectedCommunity = ''}); + {this.communityList = const [], + this.filteredCommunity = const [], + this.expandedCommunities = const [], + this.expandedSpaces = const [], + this.selectedCommunities = const [], + this.selectedSpaces = const [], + this.soldCheck = const [], + this.isSearching = false}); SpaceTreeState copyWith( - {List? communitiesList, String? selectedSpace, String? selectedCommunity}) { + {List? communitiesList, + List? filteredCommunity, + List? expandedSpaces, + List? expandedCommunity, + List? selectedCommunities, + List? selectedSpaces, + List? soldCheck, + bool? isSearching}) { return SpaceTreeState( - spacesList: communitiesList ?? this.spacesList, - selectedSpace: selectedSpace ?? this.selectedSpace, - selectedCommunity: selectedCommunity ?? this.selectedCommunity); + communityList: communitiesList ?? this.communityList, + filteredCommunity: filteredCommunity ?? this.filteredCommunity, + expandedSpaces: expandedSpaces ?? this.expandedSpaces, + expandedCommunities: expandedCommunity ?? this.expandedCommunities, + selectedCommunities: selectedCommunities ?? this.selectedCommunities, + selectedSpaces: selectedSpaces ?? this.selectedSpaces, + soldCheck: soldCheck ?? this.soldCheck, + isSearching: isSearching ?? this.isSearching); } @override - List get props => [spacesList, selectedSpace, selectedCommunity]; + List get props => [ + communityList, + filteredCommunity, + expandedSpaces, + expandedCommunities, + selectedCommunities, + selectedSpaces, + soldCheck, + isSearching + ]; } class SpaceTreeLoadingState extends SpaceTreeState {} @@ -26,6 +59,7 @@ class SpaceTreeLoadingState extends SpaceTreeState {} class SpaceTreeErrorState extends SpaceTreeState { final String message; const SpaceTreeErrorState(this.message); + @override List get props => [message]; } diff --git a/lib/pages/space_tree/view/community_tile.dart b/lib/pages/space_tree/view/community_tile.dart deleted file mode 100644 index 938aa682..00000000 --- a/lib/pages/space_tree/view/community_tile.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/space_tree/view/custom_expansion.dart'; - -class CommunityTileSpaceTree extends StatelessWidget { - final String title; - final List? children; - final bool isExpanded; - final bool isSelected; - final Function(String, bool) onExpansionChanged; - final Function() onItemSelected; - - const CommunityTileSpaceTree({ - super.key, - required this.title, - required this.isExpanded, - required this.onExpansionChanged, - required this.onItemSelected, - required this.isSelected, - this.children, - }); - - @override - Widget build(BuildContext context) { - return CustomExpansionTileSpaceTree( - title: title, - initiallyExpanded: isExpanded, - isSelected: isSelected, - onExpansionChanged: (bool expanded) { - onExpansionChanged(title, expanded); - }, - onItemSelected: onItemSelected, - children: children ?? [], - ); - } -} diff --git a/lib/pages/space_tree/view/custom_expansion.dart b/lib/pages/space_tree/view/custom_expansion.dart index 7314417b..9e40f320 100644 --- a/lib/pages/space_tree/view/custom_expansion.dart +++ b/lib/pages/space_tree/view/custom_expansion.dart @@ -1,54 +1,26 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class CustomExpansionTileSpaceTree extends StatefulWidget { +class CustomExpansionTileSpaceTree extends StatelessWidget { + final String? spaceId; final String title; final List? children; - final bool initiallyExpanded; - final bool isSelected; // Add this to track selection - final bool? isExpanded; // External control over expansion - final ValueChanged? onExpansionChanged; // Notify when expansion changes - final VoidCallback? onItemSelected; // Callback for selecting the item + final bool isSelected; + final bool isSoldCheck; + final bool isExpanded; + final Function? onExpansionChanged; + final Function? onItemSelected; - CustomExpansionTileSpaceTree({ - required this.title, - this.children, - this.initiallyExpanded = false, - this.isExpanded, // Allow external control over expansion - this.onExpansionChanged, // Notify when expansion changes - this.onItemSelected, // Trigger item selection when name is tapped - required this.isSelected, // Add this to initialize selection state - }); - - @override - CustomExpansionTileState createState() => CustomExpansionTileState(); -} - -class CustomExpansionTileState extends State { - bool _isExpanded = false; // Local expansion state - - @override - void initState() { - super.initState(); - _isExpanded = widget.initiallyExpanded; - } - - @override - void didUpdateWidget(CustomExpansionTileSpaceTree oldWidget) { - super.didUpdateWidget(oldWidget); - // Sync local state with external control of expansion state - if (widget.isExpanded != null && widget.isExpanded != _isExpanded) { - setState(() { - _isExpanded = widget.isExpanded!; - }); - } - } - - // Utility function to capitalize the first letter of the title - String _capitalizeFirstLetter(String text) { - if (text.isEmpty) return text; - return text[0].toUpperCase() + text.substring(1); - } + const CustomExpansionTileSpaceTree( + {super.key, + this.spaceId, + required this.title, + this.children, + this.isExpanded = false, + this.onExpansionChanged, + this.onItemSelected, + required this.isSelected, + this.isSoldCheck = false}); @override Widget build(BuildContext context) { @@ -56,14 +28,14 @@ class CustomExpansionTileState extends State { children: [ Row( children: [ - // Checkbox with independent state management Checkbox( - value: widget.isSelected, + value: isSoldCheck ? null : isSelected, onChanged: (bool? value) { - if (widget.onItemSelected != null) { - widget.onItemSelected!(); + if (onItemSelected != null) { + onItemSelected!(); } }, + tristate: true, side: WidgetStateBorderSide.resolveWith((states) { return const BorderSide(color: ColorsManager.grayBorder); }), @@ -76,52 +48,52 @@ class CustomExpansionTileState extends State { }), checkColor: ColorsManager.whiteColors, ), - // Expand/collapse icon, now wrapped in a GestureDetector for specific onTap - if (widget.children != null && widget.children!.isNotEmpty) - GestureDetector( + if (children != null && children!.isNotEmpty) + InkWell( onTap: () { - setState(() { - _isExpanded = !_isExpanded; - widget.onExpansionChanged?.call(_isExpanded); - }); + if (onExpansionChanged != null) { + onExpansionChanged!(); + } }, child: Icon( - _isExpanded ? Icons.keyboard_arrow_down : Icons.keyboard_arrow_right, + isExpanded ? Icons.keyboard_arrow_down : Icons.keyboard_arrow_right, color: ColorsManager.lightGrayColor, - size: 16.0, // Adjusted size for better alignment + size: 16.0, ), ), - // The title text, wrapped in GestureDetector to handle selection Expanded( child: GestureDetector( onTap: () { - if (widget.onItemSelected != null) { - widget.onItemSelected!(); + if (onItemSelected != null) { + onItemSelected!(); } }, child: Text( - _capitalizeFirstLetter(widget.title), - style: TextStyle( - color: widget.isSelected - ? ColorsManager.blackColor // Change color to black when selected - : ColorsManager.lightGrayColor, // Gray when not selected - fontWeight: FontWeight.w400, - ), + _capitalizeFirstLetter(title), + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: isSelected + ? ColorsManager.blackColor // Change color to black when selected + : ColorsManager.lightGrayColor, // Gray when not selected + fontWeight: FontWeight.w400, + ), ), ), ), ], ), - // The expanded section (children) that shows when the tile is expanded - if (_isExpanded && widget.children != null && widget.children!.isNotEmpty) + if (isExpanded && children != null && children!.isNotEmpty) Padding( - padding: const EdgeInsets.only(left: 48.0), // Indented children + padding: const EdgeInsets.only(left: 48.0), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: widget.children!, + children: children ?? [], ), ), ], ); } + + String _capitalizeFirstLetter(String text) { + if (text.isEmpty) return text; + return text[0].toUpperCase() + text.substring(1); + } } diff --git a/lib/pages/space_tree/view/side_spaces_view.dart b/lib/pages/space_tree/view/side_spaces_view.dart deleted file mode 100644 index c890ef69..00000000 --- a/lib/pages/space_tree/view/side_spaces_view.dart +++ /dev/null @@ -1,26 +0,0 @@ -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/bloc/space_tree_state.dart'; -import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; - -class SideSpacesView extends StatelessWidget { - final Function onSelectAction; - const SideSpacesView({required this.onSelectAction, super.key}); - - @override - Widget build(BuildContext context) { - return BlocConsumer( - listener: (context, state) {}, - builder: (context, state) { - return SpaceTreeView( - communities: state.spacesList, - selectedSpaceUuid: state.selectedSpace, - selectedCommunityId: state.selectedCommunity, - buildContext: context, - action: onSelectAction, - isLoading: state is SpaceTreeLoadingState, - ); - }); - } -} diff --git a/lib/pages/space_tree/view/side_tree_view.dart b/lib/pages/space_tree/view/side_tree_view.dart new file mode 100644 index 00000000..4725d09b --- /dev/null +++ b/lib/pages/space_tree/view/side_tree_view.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/common/widgets/search_bar.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart'; +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/style.dart'; + +class SideTreeView extends StatelessWidget { + const SideTreeView({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder(builder: (context, state) { + List list = state.isSearching ? state.filteredCommunity : state.communityList; + return Container( + height: MediaQuery.sizeOf(context).height, + decoration: subSectionContainerDecoration, + // padding: const EdgeInsets.all(16.0), + child: state is SpaceTreeLoadingState + ? const Center(child: CircularProgressIndicator()) + : Column( + children: [ + CustomSearchBar( + onSearchChanged: (query) { + context.read().add(SearchQueryEvent(query)); + }, + ), + const SizedBox(height: 16), + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: list.isEmpty + ? Center( + child: Text( + 'No results found', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.lightGrayColor, // Gray when not selected + fontWeight: FontWeight.w400, + ), + ), + ) + : ListView( + shrinkWrap: true, + children: list + .map( + (community) => CustomExpansionTileSpaceTree( + title: community.name, + isSelected: + state.selectedCommunities.contains(community.uuid), + isSoldCheck: + state.selectedCommunities.contains(community.uuid), + onExpansionChanged: () { + context + .read() + .add(OnCommunityExpanded(community.uuid)); + }, + isExpanded: + state.expandedCommunities.contains(community.uuid), + onItemSelected: () { + context.read().add( + OnCommunitySelected(community.uuid, community.spaces)); + }, + children: community.spaces.map((space) { + return CustomExpansionTileSpaceTree( + title: space.name, + isExpanded: state.expandedSpaces.contains(space.uuid), + onItemSelected: () { + context.read().add(OnSpaceSelected( + community.uuid, space.uuid ?? '', space.children)); + }, + onExpansionChanged: () { + 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), + children: _buildNestedSpaces( + context, state, space, community.uuid), + ); + }).toList(), + ), + ) + .toList(), + ), + ), + ), + ], + ), + ); + }); + } + + List _buildNestedSpaces( + BuildContext context, SpaceTreeState state, SpaceModel space, String communityId) { + return space.children.map((child) { + return CustomExpansionTileSpaceTree( + 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), + onItemSelected: () { + context + .read() + .add(OnSpaceSelected(communityId, child.uuid ?? '', child.children)); + }, + onExpansionChanged: () { + context.read().add(OnSpaceExpanded(communityId, child.uuid ?? '')); + }, + children: _buildNestedSpaces(context, state, child, communityId), + ); + }).toList(); + } +} diff --git a/lib/pages/space_tree/view/space_tile.dart b/lib/pages/space_tree/view/space_tile.dart deleted file mode 100644 index 12d20629..00000000 --- a/lib/pages/space_tree/view/space_tile.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/space_tree/view/custom_expansion.dart'; - -class SpaceTileSpaceTree extends StatefulWidget { - final String title; - final bool isSelected; - - final bool initiallyExpanded; - final ValueChanged onExpansionChanged; - final List? children; - final Function() onItemSelected; - - const SpaceTileSpaceTree({ - super.key, - required this.title, - required this.initiallyExpanded, - required this.onExpansionChanged, - required this.onItemSelected, - required this.isSelected, - this.children, - }); - - @override - _SpaceTileState createState() => _SpaceTileState(); -} - -class _SpaceTileState extends State { - late bool _isExpanded; - - @override - void initState() { - super.initState(); - _isExpanded = widget.initiallyExpanded; - } - - @override - Widget build(BuildContext context) { - return CustomExpansionTileSpaceTree( - isSelected: widget.isSelected, - title: widget.title, - initiallyExpanded: _isExpanded, - onItemSelected: widget.onItemSelected, - onExpansionChanged: (bool expanded) { - setState(() { - _isExpanded = expanded; - }); - widget.onExpansionChanged(expanded); - }, - children: widget.children ?? [], - ); - } -} diff --git a/lib/pages/space_tree/view/space_tree_view.dart b/lib/pages/space_tree/view/space_tree_view.dart deleted file mode 100644 index 06e77df0..00000000 --- a/lib/pages/space_tree/view/space_tree_view.dart +++ /dev/null @@ -1,251 +0,0 @@ -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'; -import 'package:syncrow_web/pages/space_tree/view/community_tile.dart'; -import 'package:syncrow_web/pages/space_tree/view/space_tile.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/style.dart'; - -class SpaceTreeView extends StatefulWidget { - final List communities; - final String? selectedCommunityId; - final String? selectedSpaceUuid; - final BuildContext buildContext; - final Function action; - final bool isLoading; - - const SpaceTreeView( - {super.key, - this.selectedCommunityId, - required this.communities, - this.selectedSpaceUuid, - required this.buildContext, - required this.action, - required this.isLoading}); - - @override - State createState() => _SpaceTreeViewState(); -} - -class _SpaceTreeViewState extends State { - String _searchQuery = ''; // Track search query - String? _selectedSpaceUuid; - String? _selectedCommunityId; - String? _expandedId; - - @override - void initState() { - super.initState(); - // _selectedId = widget.selectedSpaceUuid; // Initialize with the passed selected space UUID - _selectedCommunityId = widget.selectedCommunityId; - _selectedSpaceUuid = widget.selectedSpaceUuid; - } - - @override - void didUpdateWidget(covariant SpaceTreeView oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.selectedSpaceUuid != oldWidget.selectedSpaceUuid || - widget.selectedCommunityId != oldWidget.selectedCommunityId) { - setState(() { - // _selectedId = widget.selectedSpaceUuid; - _selectedCommunityId = widget.selectedCommunityId; - _selectedSpaceUuid = widget.selectedSpaceUuid; - }); - } - } - - // Function to filter communities based on the search query - List _filterCommunities() { - _expandedId = null; - if (_searchQuery.isEmpty) { - // Reset the selected community and space UUIDs if there's no query - // _selectedSpaceUuid = null; - // _selectedCommunityId = null; - return widget.communities; - } - - // Filter communities and expand only those that match the query - return widget.communities.where((community) { - final containsQueryInCommunity = - community.name.toLowerCase().contains(_searchQuery.toLowerCase()); - final containsQueryInSpaces = - community.spaces.any((space) => _containsQuery(space, _searchQuery.toLowerCase())); - - return containsQueryInCommunity || containsQueryInSpaces; - }).toList(); - } - - // Helper function to determine if any space or its children match the search query - bool _containsQuery(SpaceModel space, String query) { - final matchesSpace = space.name.toLowerCase().contains(query); - final matchesChildren = - space.children.any((child) => _containsQuery(child, query)); // Recursive check for children - - // If the space or any of its children match the query, expand this space - if (matchesSpace || matchesChildren) { - _expandedId = space.uuid; - } - - return matchesSpace || matchesChildren; - } - - bool _isSpaceOrChildSelected(SpaceModel space) { - // Return true if the current space or any of its child spaces is selected - if (_expandedId == space.uuid) { - return true; - } - - // Recursively check if any child spaces match the query - for (var child in space.children) { - if (_isSpaceOrChildSelected(child)) { - return true; - } - } - - return false; - } - - @override - Widget build(BuildContext context) { - final filteredCommunities = _filterCommunities(); - - return Container( - // width: MediaQuery.sizeOf(context).width * 0.25, - height: MediaQuery.sizeOf(context).height, - margin: const EdgeInsets.only(right: 24), - decoration: subSectionContainerDecoration, - child: widget.isLoading - ? const Center(child: CircularProgressIndicator()) - : Column( - mainAxisSize: MainAxisSize.min, // Ensures the Column only takes necessary height - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Communities title with the add button - Container( - decoration: subSectionContainerDecoration, - padding: const EdgeInsets.all(16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('Communities', - style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: Colors.black, - )), - GestureDetector( - onTap: () => _navigateToBlank(context), - child: Container( - width: 30, - height: 30, - decoration: const BoxDecoration( - color: ColorsManager.whiteColors, - shape: BoxShape.circle, - ), - child: Center( - child: SvgPicture.asset( - Assets.roundedAddIcon, - width: 24, - height: 24, - ), - ), - ), - ), - ], - ), - ), - // Search bar - CustomSearchBar( - onSearchChanged: (query) { - setState(() { - _selectedSpaceUuid = null; - _searchQuery = query; - }); - }, - ), - const SizedBox(height: 16), - // Community list - Expanded( - child: ListView( - children: filteredCommunities.map((community) { - return _buildCommunityTile(context, community); - }).toList(), - ), - ), - ], - ), - ); - } - - void _navigateToBlank(BuildContext context) { - setState(() { - // _selectedId = ''; - _selectedSpaceUuid = ''; - }); - // context.read().add( - // NewCommunityEvent(communities: widget.communities), - // ); - } - - Widget _buildCommunityTile(BuildContext context, CommunityModel community) { - bool hasChildren = community.spaces.isNotEmpty; - - return CommunityTileSpaceTree( - title: community.name, - key: ValueKey(community.uuid), - isSelected: _selectedCommunityId == community.uuid, - isExpanded: false, - onItemSelected: () { - setState(() { - _selectedCommunityId = community.uuid; - _selectedSpaceUuid = null; - }); - - widget.buildContext.read().add( - OnSelectSpaceEvent(community.uuid, ''), - ); - - widget.action(community.uuid, ''); - }, - onExpansionChanged: (String title, bool expanded) { - _handleExpansionChange(community.uuid, expanded); - }, - children: hasChildren - ? community.spaces.map((space) => _buildSpaceTile(space, community)).toList() - : null, - ); - } - - Widget _buildSpaceTile(SpaceModel space, CommunityModel community) { - return SpaceTileSpaceTree( - title: space.name, - key: ValueKey(space.uuid), - isSelected: _selectedSpaceUuid == space.uuid, - initiallyExpanded: _isSpaceOrChildSelected(space), - onExpansionChanged: (bool expanded) { - _handleExpansionChange(space.uuid ?? '', expanded); - }, - onItemSelected: () { - setState(() { - _selectedSpaceUuid = space.uuid; - _selectedCommunityId = community.uuid; - }); - - widget.buildContext.read().add( - OnSelectSpaceEvent(community.uuid, space.uuid ?? ''), - ); - - widget.action(community.uuid, space.uuid); - }, - children: space.children.isNotEmpty - ? space.children.map((childSpace) => _buildSpaceTile(childSpace, community)).toList() - : [], - ); - } - - void _handleExpansionChange(String uuid, bool expanded) {} -} diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index 551951ea..aae7c453 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -73,7 +73,7 @@ class SceneApi { {showInDevice = false}) async { try { final response = await _httpService.get( - path: ApiEndpoints.getScenes + path: ApiEndpoints.getUnitScenes .replaceAll('{spaceUuid}', spaceId) .replaceAll('{communityUuid}', communityId) .replaceAll('{projectId}', TempConst.projectId), From a294988558e9522a2cbd78b0f995bd0f5ac3f80a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 30 Jan 2025 10:09:21 +0400 Subject: [PATCH 150/175] add subspace and space information --- .../models/device_subspace.model.dart | 47 +++++++++++++++++++ .../all_devices/models/devices_model.dart | 11 +++++ .../shared/device_control_dialog.dart | 13 +++-- 3 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 lib/pages/device_managment/all_devices/models/device_subspace.model.dart diff --git a/lib/pages/device_managment/all_devices/models/device_subspace.model.dart b/lib/pages/device_managment/all_devices/models/device_subspace.model.dart new file mode 100644 index 00000000..dc2386de --- /dev/null +++ b/lib/pages/device_managment/all_devices/models/device_subspace.model.dart @@ -0,0 +1,47 @@ +class DeviceSubspace { + final String uuid; + final DateTime? createdAt; + final DateTime? updatedAt; + final String subspaceName; + final bool disabled; + + DeviceSubspace({ + required this.uuid, + this.createdAt, + this.updatedAt, + required this.subspaceName, + required this.disabled, + }); + + factory DeviceSubspace.fromJson(Map json) { + return DeviceSubspace( + uuid: json['uuid'] as String, + createdAt: json['createdAt'] != null + ? DateTime.tryParse(json['createdAt'].toString()) + : null, + updatedAt: json['updatedAt'] != null + ? DateTime.tryParse(json['updatedAt'].toString()) + : null, + subspaceName: json['subspaceName'] as String, + disabled: json['disabled'] as bool, + ); + } + + Map toJson() { + return { + 'uuid': uuid, + 'createdAt': createdAt?.toIso8601String(), + 'updatedAt': updatedAt?.toIso8601String(), + 'subspaceName': subspaceName, + 'disabled': disabled, + }; + } + + static List listFromJson(List jsonList) { + return jsonList.map((json) => DeviceSubspace.fromJson(json)).toList(); + } + + static List> listToJson(List subspaces) { + return subspaces.map((subspace) => subspace.toJson()).toList(); + } +} diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index b7e4f010..576e895f 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -1,5 +1,6 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_community.model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_space_model.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_subspace.model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart'; import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart'; @@ -47,6 +48,7 @@ class AllDevicesModel { */ DevicesModelRoom? room; + DeviceSubspace? subspace; DevicesModelUnit? unit; DeviceCommunityModel? community; String? productUuid; @@ -77,6 +79,7 @@ class AllDevicesModel { AllDevicesModel({ this.room, + this.subspace, this.unit, this.community, this.productUuid, @@ -110,6 +113,9 @@ class AllDevicesModel { room = (json['room'] != null && (json['room'] is Map)) ? DevicesModelRoom.fromJson(json['room']) : null; + subspace = (json['subspace'] != null && (json['subspace'] is Map)) + ? DeviceSubspace.fromJson(json['subspace']) + : null; unit = (json['unit'] != null && (json['unit'] is Map)) ? DevicesModelUnit.fromJson(json['unit']) : null; @@ -288,6 +294,9 @@ SOS if (room != null) { data['room'] = room!.toJson(); } + if (subspace != null) { + data['subspace'] = subspace!.toJson(); + } if (unit != null) { data['unit'] = unit!.toJson(); } @@ -330,6 +339,7 @@ SOS return other is AllDevicesModel && other.room == room && + other.subspace == subspace && other.unit == unit && other.productUuid == productUuid && other.productType == productType && @@ -360,6 +370,7 @@ SOS @override int get hashCode { return room.hashCode ^ + subspace.hashCode ^ unit.hashCode ^ productUuid.hashCode ^ productType.hashCode ^ diff --git a/lib/pages/device_managment/shared/device_control_dialog.dart b/lib/pages/device_managment/shared/device_control_dialog.dart index aa1153af..7304dd07 100644 --- a/lib/pages/device_managment/shared/device_control_dialog.dart +++ b/lib/pages/device_managment/shared/device_control_dialog.dart @@ -95,8 +95,9 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode { ]), TableRow( children: [ - _buildInfoRow('Space Name:', device.unit?.name ?? 'N/A'), - _buildInfoRow('Room:', device.room?.name ?? 'N/A'), + _buildInfoRow('Space Name:', + device.spaces?.firstOrNull?.spaceName ?? 'N/A'), + _buildInfoRow('Room:', device.subspace?.subspaceName ?? 'N/A'), ], ), TableRow( @@ -111,9 +112,13 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode { ), _buildInfoRow( 'Battery Level:', - device.batteryLevel != null ? '${device.batteryLevel ?? 0}%' : "-", + device.batteryLevel != null + ? '${device.batteryLevel ?? 0}%' + : "-", statusColor: device.batteryLevel != null - ? (device.batteryLevel! < 20 ? ColorsManager.red : ColorsManager.green) + ? (device.batteryLevel! < 20 + ? ColorsManager.red + : ColorsManager.green) : null, ), ], From bf5b39e7429ae70bd471eb806502c0cf0e319f5c Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 30 Jan 2025 15:00:40 +0400 Subject: [PATCH 151/175] add empty subspace validation --- .../space_model/widgets/subspace_name_label_widget.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart index 63edcf8f..6a6aec44 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart @@ -52,6 +52,13 @@ class _SubspaceNameDisplayWidgetState extends State { void _handleValidationAndSave() { final updatedName = _controller.text; + + if (updatedName.isEmpty) { + setState(() { + errorText = 'Subspace name cannot be empty.'; + }); + return; + } if (widget.validateName(updatedName)) { setState(() { errorText = null; From b070884bd96b64ef525053960547178b29cee5f2 Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 30 Jan 2025 16:43:45 +0300 Subject: [PATCH 152/175] Fix bugs related to the user table, privacy policy, and table filter. --- lib/pages/auth/bloc/auth_bloc.dart | 45 +- .../auth/model/login_with_email_model.dart | 2 + lib/pages/home/bloc/home_bloc.dart | 2 + .../view/agreement_and_privacy_dialog.dart | 10 +- lib/pages/home/view/home_page_web.dart | 2 +- .../model/roles_user_model.dart | 4 +- .../add_user_dialog/bloc/users_bloc.dart | 23 +- .../add_user_dialog/view/basics_view.dart | 2 +- .../view/popup_menu_filter.dart | 2 +- .../users_table/bloc/user_table_bloc.dart | 43 +- .../view/creation_date_filter.dart | 9 +- .../users_table/view/de_activate_filter.dart | 7 +- .../users_table/view/name_filter.dart | 9 +- .../users_table/view/user_table.dart | 486 +++++++++--------- .../users_table/view/users_page.dart | 26 +- lib/services/user_permission.dart | 7 +- pubspec.lock | 96 ++++ 17 files changed, 454 insertions(+), 321 deletions(-) diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index b931d90d..74980c2d 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -31,7 +31,8 @@ class AuthBloc extends Bloc { ////////////////////////////// forget password ////////////////////////////////// final TextEditingController forgetEmailController = TextEditingController(); - final TextEditingController forgetPasswordController = TextEditingController(); + final TextEditingController forgetPasswordController = + TextEditingController(); final TextEditingController forgetOtp = TextEditingController(); final forgetFormKey = GlobalKey(); final forgetEmailKey = GlobalKey(); @@ -48,7 +49,8 @@ class AuthBloc extends Bloc { return; } _remainingTime = 1; - add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); + add(UpdateTimerEvent( + remainingTime: _remainingTime, isButtonEnabled: false)); try { forgetEmailValidate = ''; _remainingTime = (await AuthenticationAPI.sendOtp( @@ -85,7 +87,8 @@ class AuthBloc extends Bloc { _timer?.cancel(); add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true)); } else { - add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); + add(UpdateTimerEvent( + remainingTime: _remainingTime, isButtonEnabled: false)); } }); } @@ -95,7 +98,8 @@ class AuthBloc extends Bloc { emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); } - Future changePassword(ChangePasswordEvent event, Emitter emit) async { + Future changePassword( + ChangePasswordEvent event, Emitter emit) async { emit(LoadingForgetState()); try { var response = await AuthenticationAPI.verifyOtp( @@ -111,7 +115,8 @@ class AuthBloc extends Bloc { } } on DioException catch (e) { final errorData = e.response!.data; - String errorMessage = errorData['error']['message'] ?? 'something went wrong'; + String errorMessage = + errorData['error']['message'] ?? 'something went wrong'; validate = errorMessage; emit(AuthInitialState()); } @@ -125,7 +130,9 @@ class AuthBloc extends Bloc { } void _onUpdateTimer(UpdateTimerEvent event, Emitter emit) { - emit(TimerState(isButtonEnabled: event.isButtonEnabled, remainingTime: event.remainingTime)); + emit(TimerState( + isButtonEnabled: event.isButtonEnabled, + remainingTime: event.remainingTime)); } ///////////////////////////////////// login ///////////////////////////////////// @@ -161,15 +168,23 @@ class AuthBloc extends Bloc { password: event.password, ), ); - } catch (failure) { - validate = 'Invalid Credentials!'; + } on DioException catch (e) { + final errorData = e.response!.data; + String errorMessage = errorData['error']['message']; + if (errorMessage == "Access denied for web platform") { + validate = errorMessage; + } else { + validate = 'Invalid Credentials!'; + } emit(LoginInitial()); return; } + if (token.accessTokenIsNotEmpty) { FlutterSecureStorage storage = const FlutterSecureStorage(); - await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken); + await storage.write( + key: Token.loginAccessTokenKey, value: token.accessToken); const FlutterSecureStorage().write( key: UserModel.userUuidKey, value: Token.decodeToken(token.accessToken)['uuid'].toString()); @@ -327,12 +342,14 @@ class AuthBloc extends Bloc { static Future getTokenAndValidate() async { try { const storage = FlutterSecureStorage(); - final firstLaunch = - await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true; + final firstLaunch = await SharedPreferencesHelper.readBoolFromSP( + StringsManager.firstLaunch) ?? + true; if (firstLaunch) { storage.deleteAll(); } - await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false); + await SharedPreferencesHelper.saveBoolToSP( + StringsManager.firstLaunch, false); final value = await storage.read(key: Token.loginAccessTokenKey) ?? ''; if (value.isEmpty) { return 'Token not found'; @@ -385,7 +402,9 @@ class AuthBloc extends Bloc { final String formattedTime = [ if (days > 0) '${days}d', // Append 'd' for days if (days > 0 || hours > 0) - hours.toString().padLeft(2, '0'), // Show hours if there are days or hours + hours + .toString() + .padLeft(2, '0'), // Show hours if there are days or hours minutes.toString().padLeft(2, '0'), seconds.toString().padLeft(2, '0'), ].join(':'); diff --git a/lib/pages/auth/model/login_with_email_model.dart b/lib/pages/auth/model/login_with_email_model.dart index ec3d4d98..dca3f8f9 100644 --- a/lib/pages/auth/model/login_with_email_model.dart +++ b/lib/pages/auth/model/login_with_email_model.dart @@ -21,7 +21,9 @@ class LoginWithEmailModel { return { 'email': email, 'password': password, + "platform": "web" // 'regionUuid': regionUuid, }; } } +//tst@tst.com \ No newline at end of file diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index 1d4bdf8b..57adc6ff 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -62,6 +62,7 @@ class HomeBloc extends Bloc { emit(LoadingHome()); terms = await HomeApi().fetchTerms(); add(FetchPolicyEvent()); + emit(PolicyAgreement()); } catch (e) { return; } @@ -71,6 +72,7 @@ class HomeBloc extends Bloc { try { emit(LoadingHome()); policy = await HomeApi().fetchPolicy(); + emit(PolicyAgreement()); } catch (e) { return; } diff --git a/lib/pages/home/view/agreement_and_privacy_dialog.dart b/lib/pages/home/view/agreement_and_privacy_dialog.dart index e9371ae9..67c33984 100644 --- a/lib/pages/home/view/agreement_and_privacy_dialog.dart +++ b/lib/pages/home/view/agreement_and_privacy_dialog.dart @@ -38,7 +38,7 @@ class _AgreementAndPrivacyDialogState extends State { final scrollPosition = _scrollController.position; if (scrollPosition.maxScrollExtent <= 0) { setState(() { - _isAtEnd = true; + _isAtEnd = true; }); } } @@ -63,9 +63,11 @@ class _AgreementAndPrivacyDialogState extends State { } String get _dialogTitle => - _currentPage == 2 ? 'User Agreement' : 'Privacy Policy'; + _currentPage == 1 ? 'User Agreement' : 'Privacy Policy'; - String get _dialogContent => _currentPage == 2 ? widget.terms : widget.policy; + String get _dialogContent => _currentPage == 1 ? widget.terms : widget.policy; + final String staticText = + '
If you cancel you will be logged out.
'; Widget _buildScrollableContent() { return Container( @@ -85,7 +87,7 @@ class _AgreementAndPrivacyDialogState extends State { controller: _scrollController, padding: const EdgeInsets.all(25), child: Html( - data: _dialogContent, + data: "$_dialogContent $staticText", onLinkTap: (url, attributes, element) async { if (url != null) { final uri = Uri.parse(url); diff --git a/lib/pages/home/view/home_page_web.dart b/lib/pages/home/view/home_page_web.dart index 8e7225d2..46e0a835 100644 --- a/lib/pages/home/view/home_page_web.dart +++ b/lib/pages/home/view/home_page_web.dart @@ -24,7 +24,7 @@ class HomeWebPage extends StatelessWidget { listener: (BuildContext context, state) { if (state is HomeInitial) { if (homeBloc.user!.hasAcceptedWebAgreement == false) { - Future.delayed(const Duration(seconds: 1), () { + Future.delayed(const Duration(seconds: 2), () { showDialog( context: context, barrierDismissible: false, diff --git a/lib/pages/roles_and_permission/model/roles_user_model.dart b/lib/pages/roles_and_permission/model/roles_user_model.dart index 44e3f493..e502370a 100644 --- a/lib/pages/roles_and_permission/model/roles_user_model.dart +++ b/lib/pages/roles_and_permission/model/roles_user_model.dart @@ -42,7 +42,9 @@ class RolesUserModel { invitedBy: json['invitedBy'].toString().toLowerCase().replaceAll("_", " "), phoneNumber: json['phoneNumber'], - jobTitle: json['jobTitle'] ?? "-", + jobTitle: json['jobTitle'] == null || json['jobTitle'] == " " + ? "_" + : json['jobTitle'], createdDate: json['createdDate'], createdTime: json['createdTime'], ); diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index 26a1bcc7..16d6cbe6 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -79,13 +79,14 @@ class UsersBloc extends Bloc { List updatedCommunities = []; List spacesNodes = []; - + List communityIds = []; _onLoadCommunityAndSpaces( LoadCommunityAndSpacesEvent event, Emitter emit) async { try { emit(UsersLoadingState()); List communities = await CommunitySpaceManagementApi().fetchCommunities(); + communityIds = communities.map((community) => community.uuid).toList(); updatedCommunities = await Future.wait( communities.map((community) async { List spaces = @@ -102,7 +103,6 @@ class UsersBloc extends Bloc { }).toList(), ); emit(const SpacesLoadedState()); - return updatedCommunities; } catch (e) { emit(ErrorState('Error loading communities and spaces: $e')); } @@ -177,7 +177,6 @@ class UsersBloc extends Bloc { try { emit(UsersLoadingState()); roles = await UserPermissionApi().fetchRoles(); - // add(PermissionEvent(roleUuid: roles.first.uuid)); emit(RolePermissionInitial()); } catch (e) { emit(ErrorState('Error loading communities and spaces: $e')); @@ -208,10 +207,13 @@ class UsersBloc extends Bloc { return anyMatch; } - _sendInvitUser(SendInviteUsers event, Emitter emit) async { + void _sendInvitUser(SendInviteUsers event, Emitter emit) async { try { emit(UsersLoadingState()); - List selectedIds = getSelectedIds(updatedCommunities); + List selectedIds = getSelectedIds(updatedCommunities) + .where((id) => !communityIds.contains(id)) + .toList(); + bool res = await UserPermissionApi().sendInviteUser( email: emailController.text, firstName: firstNameController.text, @@ -219,9 +221,10 @@ class UsersBloc extends Bloc { lastName: lastNameController.text, phoneNumber: phoneController.text, roleUuid: roleSelected, - spaceUuids: selectedIds, + spaceUuids: selectedIds, ); - if (res == true) { + + if (res) { showCustomDialog( barrierDismissible: false, context: event.context, @@ -248,10 +251,14 @@ class UsersBloc extends Bloc { } } + _editInviteUser(EditInviteUsers event, Emitter emit) async { try { emit(UsersLoadingState()); - List selectedIds = getSelectedIds(updatedCommunities); + List selectedIds = getSelectedIds(updatedCommunities) + .where((id) => !communityIds.contains(id)) + .toList(); + bool res = await UserPermissionApi().editInviteUser( userId: event.userId, firstName: firstNameController.text, diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart index 53d9a333..308c6c67 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart @@ -218,7 +218,7 @@ class BasicsView extends StatelessWidget { if (_blocRole.checkEmailValid != "Valid email") { return _blocRole.checkEmailValid; } - return null; + // return null; }, ), ), diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart index 120a1a3a..a7f6c2b5 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart @@ -81,7 +81,7 @@ Future showPopUpFilterMenu({ ), const Divider(), const Text( - "Filter by Status", + "Filter by ", style: TextStyle(fontWeight: FontWeight.bold), ), Container( diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart index c50667be..3d09e5b4 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart @@ -40,9 +40,7 @@ class UserTableBloc extends Bloc { roleTypes.clear(); jobTitle.clear(); createdBy.clear(); - // deActivate.clear(); users = await UserPermissionApi().fetchUsers(); - users.sort((a, b) { final dateA = _parseDateTime(a.createdDate); final dateB = _parseDateTime(b.createdDate); @@ -57,15 +55,12 @@ class UserTableBloc extends Bloc { for (var user in users) { createdBy.add(user.invitedBy.toString()); } - // for (var user in users) { - // deActivate.add(user.status.toString()); - // } initialUsers = List.from(users); roleTypes = roleTypes.toSet().toList(); jobTitle = jobTitle.toSet().toList(); createdBy = createdBy.toSet().toList(); - // deActivate = deActivate.toSet().toList(); _handlePageChange(ChangePage(1), emit); + add(ChangePage(currentPage)); emit(UsersLoadedState(users: users)); } catch (e) { emit(ErrorState(e.toString())); @@ -125,6 +120,10 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByNameAsc( SortUsersByNameAsc event, Emitter emit) { + selectedRoles.clear(); + selectedJobTitles.clear(); + selectedCreatedBy.clear(); + selectedStatuses.clear(); if (currentSortOrder == "Asc") { emit(UsersLoadingState()); currentSortOrder = ""; @@ -143,13 +142,16 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByNameDesc( SortUsersByNameDesc event, Emitter emit) { + selectedRoles.clear(); + selectedJobTitles.clear(); + selectedCreatedBy.clear(); + selectedStatuses.clear(); if (currentSortOrder == "Desc") { emit(UsersLoadingState()); currentSortOrder = ""; - users = List.from(initialUsers); // Reset to saved initial state + users = List.from(initialUsers); emit(UsersLoadedState(users: users)); } else { - // Sort descending emit(UsersLoadingState()); currentSortOrder = "Desc"; users.sort((a, b) => b.firstName!.compareTo(a.firstName!)); @@ -159,6 +161,10 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByDateNewestToOldest( DateNewestToOldestEvent event, Emitter emit) { + selectedRoles.clear(); + selectedJobTitles.clear(); + selectedCreatedBy.clear(); + selectedStatuses.clear(); if (currentSortOrderDate == "NewestToOldest") { emit(UsersLoadingState()); currentSortOrder = ""; @@ -179,6 +185,10 @@ class UserTableBloc extends Bloc { void _toggleSortUsersByDateOldestToNewest( DateOldestToNewestEvent event, Emitter emit) { + selectedRoles.clear(); + selectedJobTitles.clear(); + selectedCreatedBy.clear(); + selectedStatuses.clear(); if (currentSortOrderDate == "OldestToNewest") { emit(UsersLoadingState()); currentSortOrder = ""; @@ -337,7 +347,20 @@ class UserTableBloc extends Bloc { final filteredUsers = initialUsers.where((user) { if (selectedStatuses.isEmpty) return true; - return selectedStatuses.contains(user.status); + + return selectedStatuses.any((status) { + final userStatus = user.status?.toLowerCase() ?? ''; + switch (status.toLowerCase()) { + case 'active': + return user.isEnabled == true && userStatus != 'invited'; + case 'disabled': + return user.isEnabled == false; + case 'invited': + return userStatus == 'invited'; + default: + return false; + } + }); }).toList(); if (event.sortOrder == "Asc") { currentSortOrder = "Asc"; @@ -351,9 +374,11 @@ class UserTableBloc extends Bloc { } else { currentSortOrder = ""; } + emit(UsersLoadedState(users: filteredUsers)); } + void _resetAllFilters(Emitter emit) { selectedRoles.clear(); selectedJobTitles.clear(); diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart b/lib/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart index 45ebc3ae..7b7da0bd 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart @@ -12,7 +12,7 @@ Future showDateFilterMenu({ Overlay.of(context).context.findRenderObject() as RenderBox; final RelativeRect position = RelativeRect.fromRect( Rect.fromLTRB( - overlay.size.width / 2, + overlay.size.width / 3, 240, 0, overlay.size.height, @@ -40,7 +40,6 @@ Future showDateFilterMenu({ ), title: Text( "Sort from newest to oldest", - // style: context.textTheme.bodyMedium, style: TextStyle( color: isSelected == "NewestToOldest" ? Colors.black @@ -65,9 +64,5 @@ Future showDateFilterMenu({ ), ), ], - ).then((value) { - // setState(() { - // _isDropdownOpen = false; - // }); - }); + ).then((value) {}); } diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart b/lib/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart index e78eae6b..c8742ea5 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart @@ -40,7 +40,6 @@ Future showDeActivateFilterMenu({ ), title: Text( "Sort A to Z", - // style: context.textTheme.bodyMedium, style: TextStyle( color: isSelected == "NewestToOldest" ? Colors.black @@ -65,9 +64,5 @@ Future showDeActivateFilterMenu({ ), ), ], - ).then((value) { - // setState(() { - // _isDropdownOpen = false; - // }); - }); + ).then((value) {}); } diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/name_filter.dart b/lib/pages/roles_and_permission/users_page/users_table/view/name_filter.dart index e869e10b..5ff10b20 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/name_filter.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/name_filter.dart @@ -12,7 +12,7 @@ Future showNameMenu({ Overlay.of(context).context.findRenderObject() as RenderBox; final RelativeRect position = RelativeRect.fromRect( Rect.fromLTRB( - overlay.size.width / 25, + overlay.size.width / 35, 240, 0, overlay.size.height, @@ -40,7 +40,6 @@ Future showNameMenu({ ), title: Text( "Sort A to Z", - // style: context.textTheme.bodyMedium, style: TextStyle( color: isSelected == "Asc" ? Colors.black : Colors.blueGrey), ), @@ -61,9 +60,5 @@ Future showNameMenu({ ), ), ], - ).then((value) { - // setState(() { - // _isDropdownOpen = false; - // }); - }); + ).then((value) {}); } diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart index 92229643..b26c09c4 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/user_table.dart @@ -1,256 +1,59 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/style.dart'; -class DynamicTableScreen extends StatefulWidget { - final List titles; - final List> rows; - final void Function(int columnIndex)? onFilter; +class _HeaderColumn extends StatelessWidget { + final String title; + final double width; + final bool showFilter; + final VoidCallback? onFilter; + final Function(double) onResize; - DynamicTableScreen( - {required this.titles, required this.rows, required this.onFilter}); - - @override - _DynamicTableScreenState createState() => _DynamicTableScreenState(); -} - -class _DynamicTableScreenState extends State - with WidgetsBindingObserver { - late List columnWidths; - late double totalWidth; - - @override - void initState() { - super.initState(); - columnWidths = List.filled(widget.titles.length, 150.0); - totalWidth = columnWidths.reduce((a, b) => a + b); - WidgetsBinding.instance.addObserver(this); - } - - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } - - @override - void didChangeMetrics() { - super.didChangeMetrics(); - final newScreenWidth = MediaQuery.of(context).size.width; - setState(() { - columnWidths = List.generate(widget.titles.length, (index) { - if (index == 1) { - return newScreenWidth * - 0.12; // 20% of screen width for the second column - } else if (index == 9) { - return newScreenWidth * - 0.1; // 25% of screen width for the tenth column - } - return newScreenWidth * - 0.09; // Default to 10% of screen width for other columns - }); - }); - } + const _HeaderColumn({ + required this.title, + required this.width, + required this.showFilter, + required this.onResize, + this.onFilter, + Key? key, + }) : super(key: key); @override Widget build(BuildContext context) { - final screenWidth = MediaQuery.of(context).size.width; - if (columnWidths.every((width) => width == screenWidth * 7)) { - columnWidths = List.generate(widget.titles.length, (index) { - if (index == 1) { - return screenWidth * 0.11; - } else if (index == 9) { - return screenWidth * 0.1; - } - return screenWidth * 0.09; - }); - setState(() {}); - } - return SingleChildScrollView( - clipBehavior: Clip.none, - scrollDirection: Axis.horizontal, - child: Container( - decoration: containerDecoration.copyWith( - color: ColorsManager.whiteColors, - borderRadius: const BorderRadius.all(Radius.circular(20))), - child: FittedBox( - child: Column( + return MouseRegion( + cursor: SystemMouseCursors.resizeColumn, + child: GestureDetector( + onHorizontalDragUpdate: (details) => onResize(details.delta.dx), + child: Container( + width: width, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: const BoxDecoration( + border: Border(right: BorderSide(color: ColorsManager.boxDivider)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Container( - width: totalWidth, - decoration: containerDecoration.copyWith( - color: ColorsManager.circleRolesBackground, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(15), - topRight: Radius.circular(15))), - child: Row( - children: List.generate(widget.titles.length, (index) { - return Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - FittedBox( - child: Container( - padding: const EdgeInsets.only(left: 5, right: 5), - width: columnWidths[index], - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - child: Text( - widget.titles[index], - maxLines: 2, - style: const TextStyle( - overflow: TextOverflow.ellipsis, - fontWeight: FontWeight.w400, - fontSize: 13, - color: ColorsManager.grayColor, - ), - ), - ), - if (index != 1 && - index != 9 && - index != 8 && - index != 5) - FittedBox( - child: IconButton( - icon: SvgPicture.asset( - Assets.filterTableIcon, - fit: BoxFit.none, - ), - onPressed: () { - if (widget.onFilter != null) { - widget.onFilter!(index); - } - }, - ), - ) - ], - ), - ), - ), - GestureDetector( - onHorizontalDragUpdate: (details) { - setState(() { - columnWidths[index] = - (columnWidths[index] + details.delta.dx) - .clamp(150.0, 300.0); - totalWidth = columnWidths.reduce((a, b) => a + b); - }); - }, - child: MouseRegion( - cursor: SystemMouseCursors.resizeColumn, - child: Container( - color: Colors.green, - child: Container( - color: ColorsManager.boxDivider, - width: 1, - height: 50, - ), - ), - ), - ), - ], - ); - }), + Expanded( + child: Text( + title, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.w400, + fontSize: 13, + color: ColorsManager.grayColor, + ), ), ), - widget.rows.isEmpty - ? SizedBox( - height: MediaQuery.of(context).size.height / 2, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Column( - children: [ - SvgPicture.asset(Assets.emptyTable), - const SizedBox( - height: 15, - ), - const Text( - 'No Users', - style: TextStyle( - color: ColorsManager.lightGrayColor, - fontSize: 16, - fontWeight: FontWeight.w700), - ) - ], - ), - ], - ), - ) - : Center( - child: Container( - width: totalWidth, - decoration: containerDecoration.copyWith( - color: ColorsManager.whiteColors, - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(15), - bottomRight: Radius.circular(15))), - child: ListView.builder( - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: widget.rows.length, - itemBuilder: (context, rowIndex) { - if (columnWidths.every((width) => width == 120.0)) { - columnWidths = List.generate( - widget.titles.length, (index) { - if (index == 1) { - return screenWidth * 0.11; - } else if (index == 9) { - return screenWidth * 0.2; - } - return screenWidth * 0.11; - }); - setState(() {}); - } - final row = widget.rows[rowIndex]; - return Column( - children: [ - Container( - child: Padding( - padding: const EdgeInsets.only( - left: 5, top: 10, right: 5, bottom: 10), - child: Row( - children: - List.generate(row.length, (index) { - return SizedBox( - width: columnWidths[index], - child: SizedBox( - child: Padding( - padding: const EdgeInsets.only( - left: 15, right: 10), - child: row[index], - ), - ), - ); - }), - ), - ), - ), - if (rowIndex < widget.rows.length - 1) - Row( - children: List.generate( - widget.titles.length, (index) { - return SizedBox( - width: columnWidths[index], - child: const Divider( - color: ColorsManager.boxDivider, - thickness: 1, - height: 1, - ), - ); - }), - ), - ], - ); - }, - ), - ), - ), + if (showFilter) + IconButton( + icon: SvgPicture.asset(Assets.filterTableIcon), + onPressed: onFilter, + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + ), ], ), ), @@ -258,3 +61,204 @@ class _DynamicTableScreenState extends State ); } } + +class _TableRow extends StatelessWidget { + final List cells; + final List columnWidths; + final bool isLast; + + const _TableRow({ + required this.cells, + required this.columnWidths, + required this.isLast, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + children: [ + for (int i = 0; i < cells.length; i++) + Container( + width: columnWidths[i], + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + // decoration: BoxDecoration( + // border: Border( + // right: BorderSide(color: ColorsManager.boxDivider), + // ), + // ), + child: cells[i], + ), + ], + ), + if (!isLast) + Divider( + height: 1, + thickness: 1, + color: ColorsManager.boxDivider, + ), + ], + ); + } +} +//=========================================================================== + +class DynamicTableScreen extends StatefulWidget { + final List titles; + final List> rows; + final void Function(int columnIndex)? onFilter; + + const DynamicTableScreen({ + required this.titles, + required this.rows, + required this.onFilter, + Key? key, + }) : super(key: key); + + @override + _DynamicTableScreenState createState() => _DynamicTableScreenState(); +} + +class _DynamicTableScreenState extends State { + late List columnWidths; + final double _minColumnWidth = 100.0; + final double _maxColumnWidth = 300.0; + final double _dividerWidth = 1.0; + double _lastAvailableWidth = 0; + + @override + void initState() { + super.initState(); + columnWidths = List.filled(widget.titles.length, _minColumnWidth); + } + + void _handleColumnResize(int index, double delta) { + setState(() { + double newWidth = columnWidths[index] + delta; + newWidth = newWidth.clamp(_minColumnWidth, _maxColumnWidth); + double actualDelta = newWidth - columnWidths[index]; + if (actualDelta == 0) return; + + int nextIndex = (index + 1) % columnWidths.length; + columnWidths[index] = newWidth; + columnWidths[nextIndex] = (columnWidths[nextIndex] - actualDelta) + .clamp(_minColumnWidth, _maxColumnWidth); + }); + } + + Widget _buildHeader() { + return Container( + decoration: containerDecoration.copyWith( + color: ColorsManager.circleRolesBackground, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(15), + topRight: Radius.circular(15), + ), + ), + child: Row( + children: [ + for (int i = 0; i < widget.titles.length; i++) + _HeaderColumn( + title: widget.titles[i], + width: columnWidths[i], + showFilter: i != 1 && i != 9 && i != 8 && i != 5, + onFilter: () => widget.onFilter?.call(i), + onResize: (delta) => _handleColumnResize(i, delta), + ), + ], + ), + ); + } + + Widget _buildBody() { + if (widget.rows.isEmpty) { + return SizedBox( + height: 300, + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset(Assets.emptyTable), + const SizedBox(height: 15), + const Text( + 'No Users', + style: TextStyle( + color: ColorsManager.lightGrayColor, + fontSize: 16, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ), + ); + } + + return Container( + decoration: containerDecoration.copyWith( + color: ColorsManager.whiteColors, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(15), + bottomRight: Radius.circular(15), + ), + ), + child: Column( + children: [ + for (int rowIndex = 0; rowIndex < widget.rows.length; rowIndex++) + _TableRow( + cells: widget.rows[rowIndex], + columnWidths: columnWidths, + isLast: rowIndex == widget.rows.length - 1, + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final availableWidth = constraints.maxWidth; + final totalDividersWidth = (widget.titles.length - 1) * _dividerWidth; + + if (_lastAvailableWidth != availableWidth) { + final equalWidth = + (availableWidth - totalDividersWidth) / widget.titles.length; + final clampedWidth = + equalWidth.clamp(_minColumnWidth, _maxColumnWidth); + + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + columnWidths = List.filled(widget.titles.length, clampedWidth); + _lastAvailableWidth = availableWidth; + }); + }); + } + + final totalTableWidth = + columnWidths.fold(0.0, (sum, w) => sum + w) + totalDividersWidth; + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Container( + width: totalTableWidth, + decoration: containerDecoration.copyWith( + color: ColorsManager.whiteColors, + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(), + _buildBody(), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart index dae47196..ddfe63ff 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart @@ -108,7 +108,6 @@ class UsersPage extends StatelessWidget { final screenSize = MediaQuery.of(context).size; final _blocRole = BlocProvider.of(context); if (state is UsersLoadingState) { - _blocRole.add(ChangePage(_blocRole.currentPage)); return const Center(child: CircularProgressIndicator()); } else if (state is UsersLoadedState) { return Padding( @@ -215,7 +214,7 @@ class UsersPage extends StatelessWidget { showPopUpFilterMenu( position: RelativeRect.fromLTRB( - overlay.size.width / 4, + overlay.size.width / 5.3, 240, overlay.size.width / 4, 0, @@ -225,6 +224,7 @@ class UsersPage extends StatelessWidget { checkboxStates: checkboxStates, isSelected: _blocRole.currentSortOrder, onOkPressed: () { + searchController.clear(); _blocRole.add(FilterClearEvent()); final selectedItems = checkboxStates.entries .where((entry) => entry.value) @@ -265,6 +265,7 @@ class UsersPage extends StatelessWidget { checkboxStates: checkboxStates, isSelected: _blocRole.currentSortOrder, onOkPressed: () { + searchController.clear(); _blocRole.add(FilterClearEvent()); final selectedItems = checkboxStates.entries .where((entry) => entry.value) @@ -320,6 +321,7 @@ class UsersPage extends StatelessWidget { checkboxStates: checkboxStates, isSelected: _blocRole.currentSortOrder, onOkPressed: () { + searchController.clear(); _blocRole.add(FilterClearEvent()); final selectedItems = checkboxStates.entries .where((entry) => entry.value) @@ -343,6 +345,7 @@ class UsersPage extends StatelessWidget { for (var item in _blocRole.status) item: _blocRole.selectedStatuses.contains(item), }; + final RenderBox overlay = Overlay.of(context) .context .findRenderObject() as RenderBox; @@ -350,7 +353,7 @@ class UsersPage extends StatelessWidget { position: RelativeRect.fromLTRB( overlay.size.width / 0, 240, - overlay.size.width / 4, + overlay.size.width / 5, 0, ), list: _blocRole.status, @@ -358,8 +361,8 @@ class UsersPage extends StatelessWidget { checkboxStates: checkboxStates, isSelected: _blocRole.currentSortOrder, onOkPressed: () { + searchController.clear(); _blocRole.add(FilterClearEvent()); - final selectedItems = checkboxStates.entries .where((entry) => entry.value) .map((entry) => entry.key) @@ -410,7 +413,7 @@ class UsersPage extends StatelessWidget { return [ Text('${user.firstName} ${user.lastName}'), Text(user.email), - Text(user.jobTitle ?? '-'), + Text(user.jobTitle), Text(user.roleType ?? ''), Text(user.createdDate ?? ''), Text(user.createdTime ?? ''), @@ -427,11 +430,6 @@ class UsersPage extends StatelessWidget { userId: user.uuid, onTap: user.status != "invited" ? () { - // final newStatus = user.status == 'active' - // ? 'disabled' - // : user.status == 'disabled' - // ? 'invited' - // : 'active'; context.read().add( ChangeUserStatus( userId: user.uuid, @@ -443,10 +441,6 @@ class UsersPage extends StatelessWidget { ), Row( children: [ - // actionButton( - // title: "Activity Log", - // onTap: () {}, - // ), actionButton( title: "Edit", onTap: () { @@ -487,9 +481,7 @@ class UsersPage extends StatelessWidget { }, ).then((v) { if (v != null) { - if (v != null) { - _blocRole.add(const GetUsers()); - } + _blocRole.add(const GetUsers()); } }); }, diff --git a/lib/services/user_permission.dart b/lib/services/user_permission.dart index 527010d0..f3088794 100644 --- a/lib/services/user_permission.dart +++ b/lib/services/user_permission.dart @@ -18,8 +18,7 @@ class UserPermissionApi { showServerMessage: true, expectedResponseModel: (json) { debugPrint('fetchUsers Response: $json'); - final List data = - json['data'] ?? []; // Default to an empty list if no data + final List data = json['data'] ?? []; return data.map((item) => RolesUserModel.fromJson(item)).toList(); }, ); @@ -119,7 +118,7 @@ class UserPermissionApi { ); return response ?? 'Unknown error occurred'; } on DioException catch (e) { - final errorMessage = e.response?.data['error']; + final errorMessage = e.response?.data['error']['message']; return errorMessage; } catch (e) { return e.toString(); @@ -205,7 +204,6 @@ class UserPermissionApi { .replaceAll("{invitedUserUuid}", userUuid), body: bodya, expectedResponseModel: (json) { - print('changeUserStatusById==${json['success']}'); return json['success']; }, ); @@ -213,7 +211,6 @@ class UserPermissionApi { return response; } catch (e) { return false; - print(e); } } } diff --git a/pubspec.lock b/pubspec.lock index d1c39c65..7925b06f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -65,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f" + url: "https://pub.dev" + source: hosted + version: "0.17.3" cupertino_icons: dependency: "direct main" description: @@ -182,6 +190,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.0" + flutter_html: + dependency: "direct main" + description: + name: flutter_html + sha256: "02ad69e813ecfc0728a455e4bf892b9379983e050722b1dce00192ee2e41d1ee" + url: "https://pub.dev" + source: hosted + version: "3.0.0-beta.2" flutter_lints: dependency: "direct dev" description: @@ -280,6 +296,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + html: + dependency: transitive + description: + name: html + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + url: "https://pub.dev" + source: hosted + version: "0.15.4" http: dependency: transitive description: @@ -352,6 +376,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + list_counter: + dependency: transitive + description: + name: list_counter + sha256: c447ae3dfcd1c55f0152867090e67e219d42fe6d4f2807db4bbe8b8d69912237 + url: "https://pub.dev" + source: hosted + version: "1.0.2" logging: dependency: transitive description: @@ -629,6 +661,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79 + url: "https://pub.dev" + source: hosted + version: "6.3.9" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" uuid: dependency: "direct main" description: From 9dd6c9e1e7f8b970d896fb5a13ed8a8c4dfda1fe Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 30 Jan 2025 21:15:00 +0400 Subject: [PATCH 153/175] updated logic of adding tag to subspace --- .../assign_tag/views/assign_tag_dialog.dart | 3 + .../views/assign_tag_models_dialog.dart | 58 +++---------------- 2 files changed, 10 insertions(+), 51 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart index cb1f7b46..5376cac8 100644 --- a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart +++ b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart @@ -350,6 +350,9 @@ class AssignTagDialog extends StatelessWidget { .tags ?.any((t) => t.internalId == tag.internalId) != true) { + if (modifiedSubspaces[newIndex].tags == null) { + modifiedSubspaces[newIndex].tags = []; + } tag.location = modifiedSubspaces[newIndex].subspaceName; modifiedSubspaces[newIndex].tags?.add(tag); } diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 9696723a..436f6197 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -254,7 +254,7 @@ class AssignTagModelsDialog extends StatelessWidget { final updatedTags = List.from(state.tags); final result = - processTags(updatedTags, subspaces); + updateSubspaceTags(updatedTags, subspaces); final processedTags = result['updatedTags'] as List; @@ -305,8 +305,9 @@ class AssignTagModelsDialog extends StatelessWidget { ? () async { final updatedTags = List.from(state.tags); + final result = - processTags(updatedTags, subspaces); + updateSubspaceTags(updatedTags, subspaces); final processedTags = result['updatedTags'] as List; @@ -382,7 +383,7 @@ class AssignTagModelsDialog extends StatelessWidget { return null; } - Map processTags( + Map updateSubspaceTags( List updatedTags, List? subspaces) { final modifiedTags = List.from(updatedTags); final modifiedSubspaces = List.from(subspaces ?? []); @@ -401,29 +402,17 @@ class AssignTagModelsDialog extends StatelessWidget { final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces); - if ((tag.location == 'Main Space' || tag.location == null) && - (prevIndice == null || - modifiedSubspaces[prevIndice].subspaceName == 'Main Space')) { - continue; - } - - if ((tag.location == 'Main Space' || tag.location == null) && - prevIndice != null) { - modifiedSubspaces[prevIndice] - .tags - ?.removeWhere((t) => t.internalId == tag.internalId); - continue; - } - if ((tag.location != 'Main Space' && tag.location != null) && prevIndice == null) { final newIndex = modifiedSubspaces .indexWhere((subspace) => subspace.subspaceName == tag.location); + if (newIndex != -1) { if (modifiedSubspaces[newIndex] .tags ?.any((t) => t.internalId == tag.internalId) != true) { + modifiedSubspaces[newIndex].tags ??= []; tag.location = modifiedSubspaces[newIndex].subspaceName; modifiedSubspaces[newIndex].tags?.add(tag); } @@ -431,40 +420,6 @@ class AssignTagModelsDialog extends StatelessWidget { modifiedTags.removeWhere((t) => t.internalId == tag.internalId); continue; } - - if ((tag.location != 'Main Space' && tag.location != null) && - tag.location != modifiedSubspaces[prevIndice!].subspaceName) { - modifiedSubspaces[prevIndice] - .tags - ?.removeWhere((t) => t.internalId == tag.internalId); - final newIndex = modifiedSubspaces - .indexWhere((subspace) => subspace.subspaceName == tag.location); - if (newIndex != -1) { - if (modifiedSubspaces[newIndex] - .tags - ?.any((t) => t.internalId == tag.internalId) != - true) { - tag.location = modifiedSubspaces[newIndex].subspaceName; - modifiedSubspaces[newIndex].tags?.add(tag); - } - } - - modifiedTags.removeWhere((t) => t.internalId == tag.internalId); - continue; - } - - if ((tag.location != 'Main Space' && tag.location != null) && - tag.location == modifiedSubspaces[prevIndice!].subspaceName) { - modifiedTags.removeWhere((t) => t.internalId == tag.internalId); - continue; - } - - if ((tag.location == 'Main Space' || tag.location == null) && - prevIndice != null) { - modifiedSubspaces[prevIndice] - .tags - ?.removeWhere((t) => t.internalId == tag.internalId); - } } return { @@ -472,4 +427,5 @@ class AssignTagModelsDialog extends StatelessWidget { 'subspaces': modifiedSubspaces, }; } + } From 29c444eede18525d1ea076bec101c0f978e9e385 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 30 Jan 2025 22:01:08 +0400 Subject: [PATCH 154/175] updated tags --- .../assign_tag/views/assign_tag_dialog.dart | 121 +++---------- .../views/assign_tag_models_dialog.dart | 94 ++-------- .../spaces_management/helper/tag_helper.dart | 165 +++++++++++++++++- 3 files changed, 202 insertions(+), 178 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart index 5376cac8..dd56c0bb 100644 --- a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart +++ b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart @@ -237,7 +237,8 @@ class AssignTagDialog extends StatelessWidget { final processedTags = result['updatedTags'] as List; - final processedSubspaces = result['subspaces']; + final processedSubspaces = List.from( + result['subspaces'] as List); Navigator.of(context).pop(); @@ -276,7 +277,8 @@ class AssignTagDialog extends StatelessWidget { final processedTags = result['updatedTags'] as List; final processedSubspaces = - result['subspaces'] as List; + List.from( + result['subspaces'] as List); onSave?.call(processedTags, processedSubspaces); Navigator.of(context).pop(); } @@ -301,108 +303,31 @@ class AssignTagDialog extends StatelessWidget { List getAvailableTags( List allTags, List currentTags, Tag currentTag) { - return allTags - .where((tagValue) => !currentTags - .where((e) => e != currentTag) // Exclude the current row - .map((e) => e.tag) - .contains(tagValue)) - .toList(); + List availableTagsForTagModel = TagHelper.getAvailableTags( + allTags: allTags, + currentTags: currentTags, + currentTag: currentTag, + getTag: (tag) => tag.tag ?? '', + ); + return availableTagsForTagModel; } Map processTags( List updatedTags, List? subspaces) { - final modifiedTags = List.from(updatedTags); - final modifiedSubspaces = List.from(subspaces ?? []); - - if (subspaces != null) { - for (var subspace in subspaces) { - subspace.tags?.removeWhere( - (tag) => !modifiedTags - .any((updatedTag) => updatedTag.internalId == tag.internalId), - ); - } - } - for (var tag in modifiedTags.toList()) { - if (modifiedSubspaces.isEmpty) continue; - - final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces); - - if ((tag.location == 'Main Space' || tag.location == null) && - (prevIndice == null || - modifiedSubspaces[prevIndice].subspaceName == 'Main Space')) { - continue; - } - - if ((tag.location == 'Main Space' || tag.location == null) && - prevIndice != null) { - modifiedSubspaces[prevIndice] - .tags - ?.removeWhere((t) => t.internalId == tag.internalId); - continue; - } - - if ((tag.location != 'Main Space' && tag.location != null) && - prevIndice == null) { - final newIndex = modifiedSubspaces - .indexWhere((subspace) => subspace.subspaceName == tag.location); - if (newIndex != -1) { - if (modifiedSubspaces[newIndex] - .tags - ?.any((t) => t.internalId == tag.internalId) != - true) { - if (modifiedSubspaces[newIndex].tags == null) { - modifiedSubspaces[newIndex].tags = []; - } - tag.location = modifiedSubspaces[newIndex].subspaceName; - modifiedSubspaces[newIndex].tags?.add(tag); - } - } - modifiedTags.removeWhere((t) => t.internalId == tag.internalId); - continue; - } - - if ((tag.location != 'Main Space' && tag.location != null) && - tag.location != modifiedSubspaces[prevIndice!].subspaceName) { - modifiedSubspaces[prevIndice] - .tags - ?.removeWhere((t) => t.internalId == tag.internalId); - final newIndex = modifiedSubspaces - .indexWhere((subspace) => subspace.subspaceName == tag.location); - if (newIndex != -1) { - if (modifiedSubspaces[newIndex] - .tags - ?.any((t) => t.internalId == tag.internalId) != - true) { - tag.location = modifiedSubspaces[newIndex].subspaceName; - modifiedSubspaces[newIndex].tags?.add(tag); - } - } - - modifiedTags.removeWhere((t) => t.internalId == tag.internalId); - continue; - } - - if ((tag.location != 'Main Space' && tag.location != null) && - tag.location == modifiedSubspaces[prevIndice!].subspaceName) { - modifiedTags.removeWhere((t) => t.internalId == tag.internalId); - continue; - } - - if ((tag.location == 'Main Space' || tag.location == null) && - prevIndice != null) { - modifiedSubspaces[prevIndice] - .tags - ?.removeWhere((t) => t.internalId == tag.internalId); - } - } - - return { - 'updatedTags': modifiedTags, - 'subspaces': modifiedSubspaces, - }; + return TagHelper.updateTags( + updatedTags: updatedTags, + subspaces: subspaces, + getInternalId: (tag) => tag.internalId, + getLocation: (tag) => tag.location, + setLocation: (tag, location) => tag.location = location, + getSubspaceName: (subspace) => subspace.subspaceName, + getSubspaceTags: (subspace) => subspace.tags, + setSubspaceTags: (subspace, tags) => subspace.tags = tags, + checkTagExistInSubspace: checkTagExistInSubspace, + ); } - int? checkTagExistInSubspace(Tag tag, List? subspaces) { + int? checkTagExistInSubspace(Tag tag, List? subspaces) { if (subspaces == null) return null; for (int i = 0; i < subspaces.length; i++) { final subspace = subspaces[i]; diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 436f6197..5fdaf937 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -133,8 +133,9 @@ class AssignTagModelsDialog extends StatelessWidget { : List.generate(state.tags.length, (index) { final tag = state.tags[index]; final controller = controllers[index]; - final availableTags = getAvailableTags( - allTags ?? [], state.tags, tag); + final availableTags = + TagHelper.getAvailableTagModels( + allTags ?? [], state.tags, tag); return DataRow( cells: [ @@ -254,11 +255,15 @@ class AssignTagModelsDialog extends StatelessWidget { final updatedTags = List.from(state.tags); final result = - updateSubspaceTags(updatedTags, subspaces); + TagHelper.updateSubspaceTagModels( + updatedTags, subspaces); final processedTags = result['updatedTags'] as List; - final processedSubspaces = result['subspaces']; + final processedSubspaces = + List.from( + result['subspaces'] as List); + if (context.mounted) { Navigator.of(context).pop(); @@ -305,15 +310,17 @@ class AssignTagModelsDialog extends StatelessWidget { ? () async { final updatedTags = List.from(state.tags); - + final result = - updateSubspaceTags(updatedTags, subspaces); + TagHelper.updateSubspaceTagModels( + updatedTags, subspaces); final processedTags = result['updatedTags'] as List; final processedSubspaces = - result['subspaces'] - as List; + List.from( + result['subspaces'] + as List); Navigator.of(context) .popUntil((route) => route.isFirst); @@ -357,75 +364,4 @@ class AssignTagModelsDialog extends StatelessWidget { ), )); } - - List getAvailableTags( - List allTags, List currentTags, TagModel currentTag) { - return allTags - .where((tagValue) => !currentTags - .where((e) => e != currentTag) // Exclude the current row - .map((e) => e.tag) - .contains(tagValue)) - .toList(); - } - - int? checkTagExistInSubspace( - TagModel tag, List? subspaces) { - if (subspaces == null) return null; - for (int i = 0; i < subspaces.length; i++) { - final subspace = subspaces[i]; - if (subspace.tags == null) continue; - for (var t in subspace.tags!) { - if (tag.internalId == t.internalId) { - return i; - } - } - } - return null; - } - - Map updateSubspaceTags( - List updatedTags, List? subspaces) { - final modifiedTags = List.from(updatedTags); - final modifiedSubspaces = List.from(subspaces ?? []); - - if (subspaces != null) { - for (var subspace in subspaces) { - subspace.tags?.removeWhere( - (tag) => !modifiedTags - .any((updatedTag) => updatedTag.internalId == tag.internalId), - ); - } - } - - for (var tag in modifiedTags.toList()) { - if (modifiedSubspaces.isEmpty) continue; - - final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces); - - if ((tag.location != 'Main Space' && tag.location != null) && - prevIndice == null) { - final newIndex = modifiedSubspaces - .indexWhere((subspace) => subspace.subspaceName == tag.location); - - if (newIndex != -1) { - if (modifiedSubspaces[newIndex] - .tags - ?.any((t) => t.internalId == tag.internalId) != - true) { - modifiedSubspaces[newIndex].tags ??= []; - tag.location = modifiedSubspaces[newIndex].subspaceName; - modifiedSubspaces[newIndex].tags?.add(tag); - } - } - modifiedTags.removeWhere((t) => t.internalId == tag.internalId); - continue; - } - } - - return { - 'updatedTags': modifiedTags, - 'subspaces': modifiedSubspaces, - }; - } - } diff --git a/lib/pages/spaces_management/helper/tag_helper.dart b/lib/pages/spaces_management/helper/tag_helper.dart index 041f005f..c50e6ba8 100644 --- a/lib/pages/spaces_management/helper/tag_helper.dart +++ b/lib/pages/spaces_management/helper/tag_helper.dart @@ -7,6 +7,138 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_ import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; class TagHelper { + static Map updateTags({ + required List updatedTags, + required List? subspaces, + required String Function(T) getInternalId, + required String? Function(T) getLocation, + required void Function(T, String) setLocation, + required String Function(dynamic) getSubspaceName, + required List? Function(dynamic) getSubspaceTags, + required void Function(dynamic, List?) setSubspaceTags, + required int? Function(T, List) checkTagExistInSubspace, + }) { + final modifiedTags = List.from(updatedTags); + final modifiedSubspaces = List.from(subspaces ?? []); + + if (subspaces != null) { + for (var subspace in subspaces) { + getSubspaceTags(subspace)?.removeWhere( + (tag) => !modifiedTags.any( + (updatedTag) => getInternalId(updatedTag) == getInternalId(tag)), + ); + } + } + + for (var tag in modifiedTags.toList()) { + if (modifiedSubspaces.isEmpty) continue; + + final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces); + final tagLocation = getLocation(tag); + + if ((tagLocation == 'Main Space' || tagLocation == null) && + (prevIndice == null || + getSubspaceName(modifiedSubspaces[prevIndice]) == 'Main Space')) { + continue; + } + + if ((tagLocation == 'Main Space' || tagLocation == null) && + prevIndice != null) { + getSubspaceTags(modifiedSubspaces[prevIndice]) + ?.removeWhere((t) => getInternalId(t) == getInternalId(tag)); + continue; + } + + if ((tagLocation != 'Main Space' && tagLocation != null) && + prevIndice == null) { + final newIndex = modifiedSubspaces + .indexWhere((subspace) => getSubspaceName(subspace) == tagLocation); + + if (newIndex != -1) { + if (getSubspaceTags(modifiedSubspaces[newIndex]) + ?.any((t) => getInternalId(t) == getInternalId(tag)) != + true) { + setLocation(tag, getSubspaceName(modifiedSubspaces[newIndex])); + final subspaceTags = + getSubspaceTags(modifiedSubspaces[newIndex]) ?? []; + subspaceTags.add(tag); + setSubspaceTags(modifiedSubspaces[newIndex], subspaceTags); + } + } + + modifiedTags.removeWhere((t) => getInternalId(t) == getInternalId(tag)); + continue; + } + + if ((tagLocation != 'Main Space' && tagLocation != null) && + tagLocation != getSubspaceName(modifiedSubspaces[prevIndice!])) { + getSubspaceTags(modifiedSubspaces[prevIndice]) + ?.removeWhere((t) => getInternalId(t) == getInternalId(tag)); + + final newIndex = modifiedSubspaces + .indexWhere((subspace) => getSubspaceName(subspace) == tagLocation); + + if (newIndex != -1) { + if (getSubspaceTags(modifiedSubspaces[newIndex]) + ?.any((t) => getInternalId(t) == getInternalId(tag)) != + true) { + setLocation(tag, getSubspaceName(modifiedSubspaces[newIndex])); + final subspaceTags = + getSubspaceTags(modifiedSubspaces[newIndex]) ?? []; + subspaceTags.add(tag); + setSubspaceTags(modifiedSubspaces[newIndex], subspaceTags); + } + } + + modifiedTags.removeWhere((t) => getInternalId(t) == getInternalId(tag)); + continue; + } + + if ((tagLocation != 'Main Space' && tagLocation != null) && + tagLocation == getSubspaceName(modifiedSubspaces[prevIndice!])) { + modifiedTags.removeWhere((t) => getInternalId(t) == getInternalId(tag)); + continue; + } + + if ((tagLocation == 'Main Space' || tagLocation == null) && + prevIndice != null) { + getSubspaceTags(modifiedSubspaces[prevIndice]) + ?.removeWhere((t) => getInternalId(t) == getInternalId(tag)); + } + } + + return { + 'updatedTags': modifiedTags, + 'subspaces': modifiedSubspaces, + }; + } + + static List getAvailableTags({ + required List allTags, + required List currentTags, + required T currentTag, + required String? Function(T) getTag, // Allow nullable return type + }) { + return allTags + .where((tagValue) => !currentTags + .where((e) => e != currentTag) // Exclude the current row + .map((e) => getTag(e) ?? '') // Handle null values gracefully + .contains(tagValue)) + .toList(); + } + + static List getAvailableTagModels( + List allTags, List currentTags, TagModel currentTag) { + List availableTagsForTagModel = + TagHelper.getAvailableTags( + allTags: allTags, + currentTags: currentTags, + currentTag: currentTag, + getTag: (tag) => tag.tag ?? '', + ); + return availableTagsForTagModel; + } + static List generateInitialTags({ List? spaceTagModels, List? subspaces, @@ -36,7 +168,7 @@ class TagHelper { return initialTags; } - static List generateInitialForTags({ + static List generateInitialForTags({ List? spaceTags, List? subspaces, }) { @@ -145,4 +277,35 @@ class TagHelper { )) .toList(); } + + static int? checkTagExistInSubspaceModels(TagModel tag, List? subspaces) { + if (subspaces == null) return null; + + for (int i = 0; i < subspaces.length; i++) { + final subspace = subspaces[i] as SubspaceTemplateModel; // Explicit cast + if (subspace.tags == null) continue; + for (var t in subspace.tags!) { + if (tag.internalId == t.internalId) { + return i; + } + } + } + return null; + } + + static Map updateSubspaceTagModels( + List updatedTags, List? subspaces) { + return TagHelper.updateTags( + updatedTags: updatedTags, + subspaces: subspaces, + getInternalId: (tag) => tag.internalId, + getLocation: (tag) => tag.location, + setLocation: (tag, location) => tag.location = location, + getSubspaceName: (subspace) => subspace.subspaceName, + getSubspaceTags: (subspace) => subspace.tags, + setSubspaceTags: (subspace, tags) => subspace.tags = tags, + checkTagExistInSubspace: checkTagExistInSubspaceModels, + ); + } + } From 916b606cb1d943560532fedca031d72387a30bf4 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 30 Jan 2025 23:26:26 +0400 Subject: [PATCH 155/175] deleting subspace won't remove tag --- .../dialog/create_space_model_dialog.dart | 8 +++++- .../widgets/subspace_model_create_widget.dart | 26 ++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 212400b9..28b548b0 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -129,10 +129,16 @@ class CreateSpaceModelDialog extends StatelessWidget { const SizedBox(height: 16), SubspaceModelCreate( subspaces: state.space.subspaceModels ?? [], - onSpaceModelUpdate: (updatedSubspaces) { + tags: state.space.tags ?? [], + onSpaceModelUpdate: (updatedSubspaces,updatedTags) { context .read() .add(AddSubspacesToSpaceTemplate(updatedSubspaces)); + if(updatedTags!=null){ + context + .read() + .add(AddTagsToSpaceTemplate(updatedTags)); + } }, ), const SizedBox(height: 10), diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 3e13f9c5..d8e27bec 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/common/edit_chip.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart'; @@ -8,13 +9,16 @@ import 'package:syncrow_web/utils/color_manager.dart'; class SubspaceModelCreate extends StatefulWidget { final List subspaces; - final void Function(List newSubspaces)? + final void Function( + List newSubspaces, List? tags)? onSpaceModelUpdate; + final List tags; const SubspaceModelCreate({ Key? key, required this.subspaces, this.onSpaceModelUpdate, + required this.tags, }) : super(key: key); @override @@ -24,11 +28,13 @@ class SubspaceModelCreate extends StatefulWidget { class _SubspaceModelCreateState extends State { late List _subspaces; String? errorSubspaceId; + late List _tags; @override void initState() { super.initState(); _subspaces = List.from(widget.subspaces); + _tags = List.from(widget.tags); } @override @@ -105,14 +111,26 @@ class _SubspaceModelCreateState extends State { isEdit: true, dialogTitle: dialogTitle, existingSubSpaces: _subspaces, - onUpdate: (subspaceModels) { + final updatedIds = subspaceModels.map((s) => s.internalId).toSet(); + final deletedSubspaces = _subspaces + .where((s) => !updatedIds.contains(s.internalId)) + .toList(); + + final List tagsToAppendToSpace = []; + + for (var s in deletedSubspaces) { + if (s.tags != null) { + tagsToAppendToSpace.addAll(s.tags!); + } + } + setState(() { _subspaces = subspaceModels; - errorSubspaceId = null; + _tags.addAll(tagsToAppendToSpace); }); if (widget.onSpaceModelUpdate != null) { - widget.onSpaceModelUpdate!(subspaceModels); + widget.onSpaceModelUpdate!(subspaceModels, _tags); } }, ); From 5ae07688cb8276f9aa3a76ea92617d211594e8c3 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Sun, 2 Feb 2025 02:46:13 +0300 Subject: [PATCH 156/175] fixed issues in the space tree --- .../widgets/device_managment_body.dart | 4 +- lib/pages/routines/view/routines_view.dart | 4 +- .../space_tree/bloc/space_tree_bloc.dart | 102 +++++++++++++++--- ...de_tree_view.dart => space_tree_view.dart} | 4 +- 4 files changed, 94 insertions(+), 20 deletions(-) rename lib/pages/space_tree/view/{side_tree_view.dart => space_tree_view.dart} (98%) diff --git a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart index d97972d4..6e833787 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart @@ -8,7 +8,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart'; -import 'package:syncrow_web/pages/space_tree/view/side_tree_view.dart'; +import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; import 'package:syncrow_web/utils/format_date_time.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -63,7 +63,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { return Row( children: [ const Expanded( - child: SideTreeView( + child: SpaceTreeView( // onSelectAction: (String communityId, String spaceId) { // context.read().add(FetchDevices(communityId, spaceId)); // }, diff --git a/lib/pages/routines/view/routines_view.dart b/lib/pages/routines/view/routines_view.dart index e7601729..8485cfca 100644 --- a/lib/pages/routines/view/routines_view.dart +++ b/lib/pages/routines/view/routines_view.dart @@ -5,7 +5,7 @@ import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart'; import 'package:syncrow_web/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart'; import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_view_card.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; -import 'package:syncrow_web/pages/space_tree/view/side_tree_view.dart'; +import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; @@ -41,7 +41,7 @@ class _RoutinesViewState extends State { // // ..add(LoadAutomation(spaceId)); // }, // ) - SideTreeView()), + SpaceTreeView()), Expanded( flex: 3, child: Padding( diff --git a/lib/pages/space_tree/bloc/space_tree_bloc.dart b/lib/pages/space_tree/bloc/space_tree_bloc.dart index 78da9fd2..120ce274 100644 --- a/lib/pages/space_tree/bloc/space_tree_bloc.dart +++ b/lib/pages/space_tree/bloc/space_tree_bloc.dart @@ -83,9 +83,10 @@ class SpaceTreeBloc extends Bloc { _onCommunitySelected(OnCommunitySelected event, Emitter emit) async { try { - List updatedSelectedCommunities = List.from(state.selectedCommunities); - List updatedSelectedSpaces = List.from(state.selectedSpaces); - List updatedSoldChecks = List.from(state.soldCheck); + List updatedSelectedCommunities = + List.from(state.selectedCommunities.toSet().toList()); + List updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList()); + List updatedSoldChecks = List.from(state.soldCheck.toSet().toList()); List childrenIds = _getAllChildIds(event.children); @@ -111,13 +112,19 @@ class SpaceTreeBloc extends Bloc { _onSpaceSelected(OnSpaceSelected event, Emitter emit) async { try { - List updatedSelectedCommunities = List.from(state.selectedCommunities); - List updatedSelectedSpaces = List.from(state.selectedSpaces); - List updatedSoldChecks = List.from(state.soldCheck); + List updatedSelectedCommunities = + List.from(state.selectedCommunities.toSet().toList()); + List updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList()); + List updatedSoldChecks = List.from(state.soldCheck.toSet().toList()); List childrenIds = _getAllChildIds(event.children); + bool isChildSelected = false; - // List childrenIds = _getAllChildSpaceIds(event.communityId); + for (String id in childrenIds) { + if (updatedSelectedSpaces.contains(id)) { + isChildSelected = true; + } + } if (!updatedSelectedSpaces.contains(event.spaceId) && !updatedSoldChecks.contains(event.spaceId)) { @@ -127,20 +134,36 @@ class SpaceTreeBloc extends Bloc { if (childrenIds.isNotEmpty) { updatedSelectedSpaces.addAll(childrenIds); } - } else if (!updatedSoldChecks.contains(event.spaceId) && childrenIds.isNotEmpty) { + + List spaces = _getThePathToChild(event.communityId, event.spaceId); + for (String space in spaces) { + if (!updatedSelectedSpaces.contains(space) && !updatedSoldChecks.contains(space)) { + updatedSoldChecks.add(space); + } + } + } else if (!updatedSoldChecks.contains(event.spaceId) && + childrenIds.isNotEmpty && + isChildSelected) { // Second click: Unselect space but keep children updatedSelectedSpaces.remove(event.spaceId); updatedSoldChecks.add(event.spaceId); } else { // Third click: Unselect space and all its children - if (!_anySpacesSelectedInCommunity(event.communityId)) { - updatedSelectedCommunities.remove(event.communityId); - } updatedSelectedSpaces.remove(event.spaceId); if (childrenIds.isNotEmpty) { updatedSelectedSpaces.removeWhere(childrenIds.contains); + updatedSoldChecks.removeWhere(childrenIds.contains); } updatedSoldChecks.remove(event.spaceId); + + List parents = _getThePathToChild(event.communityId, event.spaceId); + if (!_parentSelected(parents, updatedSelectedSpaces)) { + updatedSoldChecks.removeWhere(parents.contains); + } + if (!_anySpacesSelectedInCommunity( + event.communityId, updatedSelectedSpaces, updatedSoldChecks)) { + updatedSelectedCommunities.remove(event.communityId); + } } emit(state.copyWith( @@ -153,6 +176,15 @@ class SpaceTreeBloc extends Bloc { } } + _parentSelected(List parents, List selectedSpaces) { + for (String space in parents) { + if (selectedSpaces.contains(space)) { + return true; + } + } + return false; + } + _onSearch(SearchQueryEvent event, Emitter emit) async { try { List communities = List.from(state.communityList); @@ -193,17 +225,59 @@ class SpaceTreeBloc extends Bloc { return ids; } - bool _anySpacesSelectedInCommunity(String communityId) { + bool _anySpacesSelectedInCommunity( + String communityId, List selectedSpaces, List partialCheckedList) { bool result = false; for (var community in state.communityList) { if (community.uuid == communityId) { List ids = _getAllChildIds(community.spaces); for (var id in ids) { - result = state.selectedSpaces.contains(id); - break; + result = selectedSpaces.contains(id) || partialCheckedList.contains(id); + if (result) { + return result; + } } } } return result; } + + List _getThePathToChild(String communityId, String selectedSpaceId) { + List ids = []; + for (var community in state.communityList) { + if (community.uuid == communityId) { + for (var space in community.spaces) { + List list = []; + list.add(space.uuid!); + ids = _getAllParentsIds(space, selectedSpaceId, List.from(list)); + if (ids.isNotEmpty) { + return ids; + } + } + } + } + return ids; + } + + List _getAllParentsIds(SpaceModel child, String spaceId, List listIds) { + List ids = listIds; + + ids.add(child.uuid ?? ''); + + if (child.uuid == spaceId) { + return ids; + } + + if (child.children.isNotEmpty) { + for (var space in child.children) { + var result = _getAllParentsIds(space, spaceId, List.from(ids)); + if (result.isNotEmpty) { + return result; + } + } + } + + ids.removeLast(); + return []; + } } diff --git a/lib/pages/space_tree/view/side_tree_view.dart b/lib/pages/space_tree/view/space_tree_view.dart similarity index 98% rename from lib/pages/space_tree/view/side_tree_view.dart rename to lib/pages/space_tree/view/space_tree_view.dart index 4725d09b..f5dfbc84 100644 --- a/lib/pages/space_tree/view/side_tree_view.dart +++ b/lib/pages/space_tree/view/space_tree_view.dart @@ -10,8 +10,8 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/style.dart'; -class SideTreeView extends StatelessWidget { - const SideTreeView({super.key}); +class SpaceTreeView extends StatelessWidget { + const SpaceTreeView({super.key}); @override Widget build(BuildContext context) { From 91dfd53477bf199e9f98938a425eeed85a8f868a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 2 Feb 2025 21:02:58 +0400 Subject: [PATCH 157/175] fixed issue in creating space --- .../widgets/community_structure_widget.dart | 15 ++++-- .../widgets/dialogs/create_space_dialog.dart | 22 ++++++-- .../widgets/loaded_space_widget.dart | 53 +++++++++++++------ .../assign_tag/views/assign_tag_dialog.dart | 36 ++----------- .../spaces_management/helper/tag_helper.dart | 33 +++++++++++- .../space_model/view/space_model_page.dart | 8 ++- 6 files changed, 110 insertions(+), 57 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 3a208a0d..210bda3a 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -195,7 +195,6 @@ class _CommunityStructureAreaState extends State { screenSize, position: spaces[index].position + newPosition, - parentIndex: index, direction: direction, ); @@ -355,7 +354,7 @@ class _CommunityStructureAreaState extends State { tags: widget.selectedSpace?.tags, subspaces: widget.selectedSpace?.subspaces, isEdit: true, - allTags: _getAllTagValues(spaces), + allTags: _getAllTagValues(spaces), onCreateSpace: (String name, String icon, List selectedProducts, @@ -374,6 +373,15 @@ class _CommunityStructureAreaState extends State { widget.selectedSpace!.status = SpaceStatus.modified; // Mark as modified } + + for (var space in spaces) { + if (space.internalId == widget.selectedSpace?.internalId) { + space.status = SpaceStatus.modified; + space.subspaces = subspaces; + space.tags = tags; + space.name = name; + } + } }); }, key: Key(widget.selectedSpace!.name), @@ -452,7 +460,6 @@ class _CommunityStructureAreaState extends State { }).toList(); if (spacesToSave.isEmpty) { - debugPrint("No new or modified spaces to save."); return; } @@ -748,7 +755,7 @@ class _CommunityStructureAreaState extends State { } } - List _getAllTagValues(List spaces) { + List _getAllTagValues(List spaces) { final List allTags = []; for (final space in spaces) { if (space.tags != null) { diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index ada60850..2a0050b2 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -457,7 +457,7 @@ class CreateSpaceDialogState extends State { context: context, builder: (context) => AssignTagDialog( products: widget.products, - subspaces: widget.subspaces, + subspaces: subspaces, addedProducts: TagHelper .createInitialSelectedProductsForTags( tags ?? [], subspaces), @@ -488,7 +488,6 @@ class CreateSpaceDialogState extends State { enteredName, widget.isEdit, widget.products, - subspaces, ); }, style: TextButton.styleFrom( @@ -617,9 +616,26 @@ class CreateSpaceDialogState extends State { products: products, existingSubSpaces: existingSubSpaces, onSave: (slectedSubspaces) { + final List tagsToAppendToSpace = []; + + if (slectedSubspaces != null) { + final updatedIds = + slectedSubspaces.map((s) => s.internalId).toSet(); + if (existingSubSpaces != null) { + final deletedSubspaces = existingSubSpaces + .where((s) => !updatedIds.contains(s.internalId)) + .toList(); + for (var s in deletedSubspaces) { + if (s.tags != null) { + tagsToAppendToSpace.addAll(s.tags!); + } + } + } + } if (slectedSubspaces != null) { setState(() { subspaces = slectedSubspaces; + tags?.addAll(tagsToAppendToSpace); selectedSpaceModel = null; }); } @@ -629,7 +645,7 @@ class CreateSpaceDialogState extends State { } void _showTagCreateDialog(BuildContext context, String name, bool isEdit, - List? products, List? subspaces) { + List? products) { isEdit ? showDialog( context: context, 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 cdba0a5a..f1e9e753 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 @@ -11,7 +11,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/space_tem import 'package:syncrow_web/pages/spaces_management/space_model/view/space_model_page.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; -class LoadedSpaceView extends StatelessWidget { +class LoadedSpaceView extends StatefulWidget { final List communities; final CommunityModel? selectedCommunity; final SpaceModel? selectedSpace; @@ -26,41 +26,64 @@ class LoadedSpaceView extends StatelessWidget { this.selectedSpace, this.products, this.spaceModels, - required this.shouldNavigateToSpaceModelPage + required this.shouldNavigateToSpaceModelPage, }); @override - Widget build(BuildContext context) { + _LoadedSpaceViewState createState() => _LoadedSpaceViewState(); +} +class _LoadedSpaceViewState extends State { + late List _spaceModels; + + @override + void initState() { + super.initState(); + _spaceModels = widget.spaceModels ?? []; + } + + void _onSpaceModelsUpdated(List updatedModels) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + _spaceModels = updatedModels; + }); + }); + } + + @override + Widget build(BuildContext context) { return Stack( clipBehavior: Clip.none, children: [ Row( children: [ SidebarWidget( - communities: communities, - selectedSpaceUuid: - selectedSpace?.uuid ?? selectedCommunity?.uuid ?? '', + communities: widget.communities, + selectedSpaceUuid: widget.selectedSpace?.uuid ?? + widget.selectedCommunity?.uuid ?? + '', ), - shouldNavigateToSpaceModelPage + widget.shouldNavigateToSpaceModelPage ? Expanded( child: BlocProvider( create: (context) => SpaceModelBloc( api: SpaceModelManagementApi(), - initialSpaceModels: spaceModels ?? [], + initialSpaceModels: _spaceModels, ), child: SpaceModelPage( - products: products, + products: widget.products, + onSpaceModelsUpdated: + _onSpaceModelsUpdated, // Pass callback ), ), ) : CommunityStructureArea( - selectedCommunity: selectedCommunity, - selectedSpace: selectedSpace, - spaces: selectedCommunity?.spaces ?? [], - products: products, - communities: communities, - spaceModels: spaceModels, + selectedCommunity: widget.selectedCommunity, + selectedSpace: widget.selectedSpace, + spaces: widget.selectedCommunity?.spaces ?? [], + products: widget.products, + communities: widget.communities, + spaceModels: _spaceModels, ), ], ), diff --git a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart index dd56c0bb..0e08de53 100644 --- a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart +++ b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart @@ -233,7 +233,8 @@ class AssignTagDialog extends StatelessWidget { label: 'Add New Device', onPressed: () async { final updatedTags = List.from(state.tags); - final result = processTags(updatedTags, subspaces); + final result = + TagHelper.processTags(updatedTags, subspaces); final processedTags = result['updatedTags'] as List; @@ -271,8 +272,8 @@ class AssignTagDialog extends StatelessWidget { onPressed: state.isSaveEnabled ? () async { final updatedTags = List.from(state.tags); - final result = - processTags(updatedTags, subspaces); + final result = TagHelper.processTags( + updatedTags, subspaces); final processedTags = result['updatedTags'] as List; @@ -311,33 +312,4 @@ class AssignTagDialog extends StatelessWidget { ); return availableTagsForTagModel; } - - Map processTags( - List updatedTags, List? subspaces) { - return TagHelper.updateTags( - updatedTags: updatedTags, - subspaces: subspaces, - getInternalId: (tag) => tag.internalId, - getLocation: (tag) => tag.location, - setLocation: (tag, location) => tag.location = location, - getSubspaceName: (subspace) => subspace.subspaceName, - getSubspaceTags: (subspace) => subspace.tags, - setSubspaceTags: (subspace, tags) => subspace.tags = tags, - checkTagExistInSubspace: checkTagExistInSubspace, - ); - } - - int? checkTagExistInSubspace(Tag tag, List? subspaces) { - if (subspaces == null) return null; - for (int i = 0; i < subspaces.length; i++) { - final subspace = subspaces[i]; - if (subspace.tags == null) continue; - for (var t in subspace.tags!) { - if (tag.internalId == t.internalId) { - return i; - } - } - } - return null; - } } diff --git a/lib/pages/spaces_management/helper/tag_helper.dart b/lib/pages/spaces_management/helper/tag_helper.dart index c50e6ba8..757f46e1 100644 --- a/lib/pages/spaces_management/helper/tag_helper.dart +++ b/lib/pages/spaces_management/helper/tag_helper.dart @@ -278,7 +278,8 @@ class TagHelper { .toList(); } - static int? checkTagExistInSubspaceModels(TagModel tag, List? subspaces) { + static int? checkTagExistInSubspaceModels( + TagModel tag, List? subspaces) { if (subspaces == null) return null; for (int i = 0; i < subspaces.length; i++) { @@ -293,7 +294,7 @@ class TagHelper { return null; } - static Map updateSubspaceTagModels( + static Map updateSubspaceTagModels( List updatedTags, List? subspaces) { return TagHelper.updateTags( updatedTags: updatedTags, @@ -308,4 +309,32 @@ class TagHelper { ); } + static int? checkTagExistInSubspace(Tag tag, List? subspaces) { + if (subspaces == null) return null; + for (int i = 0; i < subspaces.length; i++) { + final subspace = subspaces[i]; + if (subspace.tags == null) continue; + for (var t in subspace.tags!) { + if (tag.internalId == t.internalId) { + return i; + } + } + } + return null; + } + + static Map processTags( + List updatedTags, List? subspaces) { + return TagHelper.updateTags( + updatedTags: updatedTags, + subspaces: subspaces, + getInternalId: (tag) => tag.internalId, + getLocation: (tag) => tag.location, + setLocation: (tag, location) => tag.location = location, + getSubspaceName: (subspace) => subspace.subspaceName, + getSubspaceTags: (subspace) => subspace.tags, + setSubspaceTags: (subspace, tags) => subspace.tags = tags, + checkTagExistInSubspace: checkTagExistInSubspace, + ); + } } diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index b1fce7a1..8f466a5e 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -11,8 +11,10 @@ import 'package:syncrow_web/utils/color_manager.dart'; class SpaceModelPage extends StatelessWidget { final List? products; + final Function(List)? onSpaceModelsUpdated; - const SpaceModelPage({Key? key, this.products}) : super(key: key); + const SpaceModelPage({Key? key, this.products, this.onSpaceModelsUpdated}) + : super(key: key); @override Widget build(BuildContext context) { @@ -25,6 +27,10 @@ class SpaceModelPage extends StatelessWidget { final allTagValues = _getAllTagValues(spaceModels); final allSpaceModelNames = _getAllSpaceModelName(spaceModels); + if (onSpaceModelsUpdated != null) { + onSpaceModelsUpdated!(spaceModels); + } + return Scaffold( backgroundColor: ColorsManager.whiteColors, body: Padding( From e6e46be9b4488b17eb89432dc91b5774cf67daa6 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 2 Feb 2025 23:16:34 +0400 Subject: [PATCH 158/175] fixed issues in create space and duplicate --- .../bloc/space_management_bloc.dart | 2 + .../all_spaces/model/space_model.dart | 3 ++ .../widgets/community_structure_widget.dart | 14 +++--- .../widgets/dialogs/create_space_dialog.dart | 28 +++++++++--- .../widgets/loaded_space_widget.dart | 28 ++++++------ .../helper/space_helper.dart | 43 +++++++++++++++++++ .../models/space_template_model.dart | 1 - 7 files changed, 90 insertions(+), 29 deletions(-) create mode 100644 lib/pages/spaces_management/helper/space_helper.dart diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 1b5692c6..31e19af4 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -179,6 +179,7 @@ class SpaceManagementBloc final updatedCommunities = await Future.wait(communities.map((community) async { final spaces = await _fetchSpacesForCommunity(community.uuid); + return CommunityModel( uuid: community.uuid, createdAt: community.createdAt, @@ -313,6 +314,7 @@ class SpaceManagementBloc SelectSpaceEvent event, Emitter emit, ) { + _handleCommunitySpaceStateUpdate( emit: emit, selectedCommunity: event.selectedCommunity, diff --git a/lib/pages/spaces_management/all_spaces/model/space_model.dart b/lib/pages/spaces_management/all_spaces/model/space_model.dart index 6ad91dad..71d365ca 100644 --- a/lib/pages/spaces_management/all_spaces/model/space_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/space_model.dart @@ -95,6 +95,9 @@ class SpaceModel { icon: json['icon'] ?? Assets.location, position: Offset(json['x'] ?? 0, json['y'] ?? 0), isHovered: false, + spaceModel: json['spaceModel'] != null + ? SpaceTemplateModel.fromJson(json['spaceModel']) + : null, tags: (json['tags'] as List?) ?.where((item) => item is Map) // Validate type .map((item) => Tag.fromJson(item as Map)) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 210bda3a..2ae20b9e 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -22,6 +22,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/curved_li import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_card_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_container_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/helper/space_helper.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -341,6 +342,7 @@ class _CommunityStructureAreaState extends State { } void _showEditSpaceDialog() { + print("parentsdf space is ${widget.selectedSpace?.parent?.name}"); if (widget.selectedSpace != null) { showDialog( context: context, @@ -350,7 +352,10 @@ class _CommunityStructureAreaState extends State { spaceModels: widget.spaceModels, name: widget.selectedSpace!.name, icon: widget.selectedSpace!.icon, + parentSpace: SpaceHelper.findSpaceByInternalId( + widget.selectedSpace?.parent?.internalId, spaces), editSpace: widget.selectedSpace, + currentSpaceModel: widget.selectedSpace?.spaceModel, tags: widget.selectedSpace?.tags, subspaces: widget.selectedSpace?.subspaces, isEdit: true, @@ -650,12 +655,6 @@ class _CommunityStructureAreaState extends State { final Map nameCounters = {}; - String _generateCopyName(String originalName) { - final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim(); - nameCounters[baseName] = (nameCounters[baseName] ?? 0) + 1; - return "$baseName(${nameCounters[baseName]})"; - } - SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition, SpaceModel? duplicatedParent) { Offset newPosition = parentPosition + Offset(horizontalGap, 0); @@ -666,7 +665,8 @@ class _CommunityStructureAreaState extends State { newPosition += Offset(horizontalGap, 0); } - final duplicatedName = _generateCopyName(original.name); + final duplicatedName = + SpaceHelper.generateUniqueSpaceName(original.name, spaces); final duplicated = SpaceModel( name: duplicatedName, diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 2a0050b2..a435a8fc 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -40,6 +40,7 @@ class CreateSpaceDialog extends StatefulWidget { final List? subspaces; final List? tags; final List? allTags; + final SpaceTemplateModel? currentSpaceModel; const CreateSpaceDialog( {super.key, @@ -54,7 +55,8 @@ class CreateSpaceDialog extends StatefulWidget { this.selectedProducts = const [], this.spaceModels, this.subspaces, - this.tags}); + this.tags, + this.currentSpaceModel}); @override CreateSpaceDialogState createState() => CreateSpaceDialogState(); @@ -83,6 +85,7 @@ class CreateSpaceDialogState extends State { enteredName.isNotEmpty || nameController.text.isNotEmpty; tags = widget.tags ?? []; subspaces = widget.subspaces ?? []; + selectedSpaceModel = widget.currentSpaceModel; } @override @@ -570,12 +573,23 @@ class CreateSpaceDialogState extends State { } bool _isNameConflict(String value) { - return (widget.parentSpace?.children.any((child) => child.name == value) ?? - false) || - (widget.parentSpace?.name == value) || - (widget.editSpace?.parent?.name == value) || - (widget.editSpace?.children.any((child) => child.name == value) ?? - false); + final parentSpace = widget.parentSpace; + final editSpace = widget.editSpace; + final siblings = parentSpace?.children + .where((child) => child.uuid != editSpace?.uuid) + .toList() ?? + []; + final siblingConflict = siblings.any((child) => child.name == value); + final parentConflict = + parentSpace?.name == value && parentSpace?.uuid != editSpace?.uuid; + final parentOfEditSpaceConflict = editSpace?.parent?.name == value && + editSpace?.parent?.uuid != editSpace?.uuid; + final childConflict = + editSpace?.children.any((child) => child.name == value) ?? false; + return siblingConflict || + parentConflict || + parentOfEditSpaceConflict || + childConflict; } void _showLinkSpaceModelDialog(BuildContext context) { 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 f1e9e753..0ce06c7c 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 @@ -39,15 +39,25 @@ class _LoadedSpaceViewState extends State { @override void initState() { super.initState(); - _spaceModels = widget.spaceModels ?? []; + _spaceModels = List.from(widget.spaceModels ?? []); + } + + @override + void didUpdateWidget(covariant LoadedSpaceView oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.spaceModels != oldWidget.spaceModels) { + setState(() { + _spaceModels = List.from(widget.spaceModels ?? []); + }); + } } void _onSpaceModelsUpdated(List updatedModels) { - WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted && updatedModels != _spaceModels) { setState(() { _spaceModels = updatedModels; }); - }); + } } @override @@ -72,8 +82,7 @@ class _LoadedSpaceViewState extends State { ), child: SpaceModelPage( products: widget.products, - onSpaceModelsUpdated: - _onSpaceModelsUpdated, // Pass callback + onSpaceModelsUpdated: _onSpaceModelsUpdated, ), ), ) @@ -91,13 +100,4 @@ class _LoadedSpaceViewState extends State { ], ); } - - SpaceModel? findSpaceByUuid(String? uuid, List communities) { - for (var community in communities) { - for (var space in community.spaces) { - if (space.uuid == uuid) return space; - } - } - return null; - } } diff --git a/lib/pages/spaces_management/helper/space_helper.dart b/lib/pages/spaces_management/helper/space_helper.dart new file mode 100644 index 00000000..75aebce9 --- /dev/null +++ b/lib/pages/spaces_management/helper/space_helper.dart @@ -0,0 +1,43 @@ +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'; + +class SpaceHelper { + static SpaceModel? findSpaceByUuid( + String? uuid, List communities) { + for (var community in communities) { + for (var space in community.spaces) { + if (space.uuid == uuid) return space; + } + } + return null; + } + + static SpaceModel? findSpaceByInternalId( + String? internalId, List spaces) { + if (internalId != null) { + for (var space in spaces) { + if (space.internalId == internalId) return space; + } + } + + return null; + } + + static String generateUniqueSpaceName( + String originalName, List spaces) { + final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim(); + int maxNumber = 0; + + for (var space in spaces) { + final match = RegExp(r'^(.*?)\((\d+)\)$').firstMatch(space.name); + if (match != null && match.group(1)?.trim() == baseName) { + int existingNumber = int.parse(match.group(2)!); + if (existingNumber > maxNumber) { + maxNumber = existingNumber; + } + } + } + + return "$baseName(${maxNumber + 1})"; + } +} 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 5edf912f..22378f1f 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 @@ -1,5 +1,4 @@ import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; From 64e3fb7f3464a43b847bbf3c73a3113435f30aed Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 2 Feb 2025 23:38:04 +0400 Subject: [PATCH 159/175] removed print --- .../all_spaces/widgets/community_structure_widget.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 2ae20b9e..795a140f 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -342,7 +342,6 @@ class _CommunityStructureAreaState extends State { } void _showEditSpaceDialog() { - print("parentsdf space is ${widget.selectedSpace?.parent?.name}"); if (widget.selectedSpace != null) { showDialog( context: context, From 506531e16a480c5339c59f5b728d92acf337095b Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Mon, 3 Feb 2025 11:15:36 +0300 Subject: [PATCH 160/175] Fetch devices based on selection --- .../device_mgmt_bloc/device_managment_bloc.dart | 16 +++++++++++++++- .../device_mgmt_bloc/device_managment_event.dart | 9 +++++---- .../all_devices/view/device_managment_page.dart | 2 +- .../widgets/device_managment_body.dart | 12 ++++++------ .../widgets/device_search_filters.dart | 2 +- lib/pages/routines/view/routines_view.dart | 6 ++++-- lib/pages/space_tree/bloc/space_tree_bloc.dart | 4 ++-- lib/pages/space_tree/bloc/space_tree_state.dart | 13 +++++++++---- lib/pages/space_tree/view/space_tree_view.dart | 7 ++++++- 9 files changed, 49 insertions(+), 22 deletions(-) diff --git a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart index db1dd2c7..6df5a780 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart @@ -1,6 +1,8 @@ import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; part 'device_managment_event.dart'; @@ -32,7 +34,19 @@ class DeviceManagementBloc extends Bloc _onFetchDevices(FetchDevices event, Emitter emit) async { emit(DeviceManagementLoading()); try { - final devices = await DevicesManagementApi().fetchDevices(event.communityId, event.spaceId); + List devices = []; + var spaceBloc = event.context.read(); + if (spaceBloc.state.selectedCommunities.isEmpty) { + devices = await DevicesManagementApi().fetchDevices('', ''); + } else { + for (var community in spaceBloc.state.selectedCommunities) { + List spacesList = spaceBloc.state.selectedCommunityAndSpaces[community] ?? []; + for (var space in spacesList) { + await DevicesManagementApi().fetchDevices(community, space); + } + } + } + _selectedDevices.clear(); _devices = devices; _filteredDevices = devices; diff --git a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_event.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_event.dart index da52249c..9928c50e 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_event.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_event.dart @@ -8,12 +8,13 @@ abstract class DeviceManagementEvent extends Equatable { } class FetchDevices extends DeviceManagementEvent { - final String communityId; - final String spaceId; + // final Map> selectedCommunitiesSpaces; + // final String spaceId; + final BuildContext context; - const FetchDevices(this.communityId, this.spaceId); + const FetchDevices(this.context); @override - List get props => [communityId, spaceId]; + List get props => [context]; } class FilterDevices extends DeviceManagementEvent { diff --git a/lib/pages/device_managment/all_devices/view/device_managment_page.dart b/lib/pages/device_managment/all_devices/view/device_managment_page.dart index 114c1fac..d29246ad 100644 --- a/lib/pages/device_managment/all_devices/view/device_managment_page.dart +++ b/lib/pages/device_managment/all_devices/view/device_managment_page.dart @@ -19,7 +19,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { return MultiBlocProvider( providers: [ BlocProvider( - create: (context) => DeviceManagementBloc()..add(const FetchDevices('', '')), + create: (context) => DeviceManagementBloc()..add(FetchDevices(context)), ), ], child: WebScaffold( diff --git a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart index 6e833787..11c692fe 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart @@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.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/utils/format_date_time.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; @@ -62,12 +63,11 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { return Row( children: [ - const Expanded( - child: SpaceTreeView( - // onSelectAction: (String communityId, String spaceId) { - // context.read().add(FetchDevices(communityId, spaceId)); - // }, - )), + Expanded(child: SpaceTreeView( + onSelect: () { + context.read().add(FetchDevices(context)); + }, + )), Expanded( flex: 3, child: state is DeviceManagementLoading diff --git a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart index 0f86ef15..650c0d21 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart @@ -85,7 +85,7 @@ class _DeviceSearchFiltersState extends State with HelperRe productNameController.clear(); context.read() ..add(ResetFilters()) - ..add(const FetchDevices('', '')); + ..add(FetchDevices(context)); }, ); } diff --git a/lib/pages/routines/view/routines_view.dart b/lib/pages/routines/view/routines_view.dart index 8485cfca..2f7daff8 100644 --- a/lib/pages/routines/view/routines_view.dart +++ b/lib/pages/routines/view/routines_view.dart @@ -32,7 +32,7 @@ class _RoutinesViewState extends State { } return Row( children: [ - const Expanded( + Expanded( child: // SideSpacesView( // onSelectAction: (String communityId, String spaceId) { @@ -41,7 +41,9 @@ class _RoutinesViewState extends State { // // ..add(LoadAutomation(spaceId)); // }, // ) - SpaceTreeView()), + SpaceTreeView( + onSelect: () {}, + )), Expanded( flex: 3, child: Padding( diff --git a/lib/pages/space_tree/bloc/space_tree_bloc.dart b/lib/pages/space_tree/bloc/space_tree_bloc.dart index 120ce274..75aa58ce 100644 --- a/lib/pages/space_tree/bloc/space_tree_bloc.dart +++ b/lib/pages/space_tree/bloc/space_tree_bloc.dart @@ -6,8 +6,8 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model import 'package:syncrow_web/services/space_mana_api.dart'; class SpaceTreeBloc extends Bloc { - String selectedCommunityId = ''; - String selectedSpaceId = ''; + String selectedCommunityId = '9cb9da1f-adbe-4688-bf55-29e7584007a7'; + String selectedSpaceId = '68dd94cf-0240-4ccf-8c26-df506246a0dd'; SpaceTreeBloc() : super(const SpaceTreeState()) { on(_fetchSpaces); diff --git a/lib/pages/space_tree/bloc/space_tree_state.dart b/lib/pages/space_tree/bloc/space_tree_state.dart index b5c987ef..1abd7455 100644 --- a/lib/pages/space_tree/bloc/space_tree_state.dart +++ b/lib/pages/space_tree/bloc/space_tree_state.dart @@ -2,6 +2,7 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; class SpaceTreeState extends Equatable { + final Map> selectedCommunityAndSpaces; final List communityList; final List filteredCommunity; final List expandedCommunities; @@ -19,7 +20,8 @@ class SpaceTreeState extends Equatable { this.selectedCommunities = const [], this.selectedSpaces = const [], this.soldCheck = const [], - this.isSearching = false}); + this.isSearching = false, + this.selectedCommunityAndSpaces = const {}}); SpaceTreeState copyWith( {List? communitiesList, @@ -29,7 +31,8 @@ class SpaceTreeState extends Equatable { List? selectedCommunities, List? selectedSpaces, List? soldCheck, - bool? isSearching}) { + bool? isSearching, + Map>? selectedCommunityAndSpaces}) { return SpaceTreeState( communityList: communitiesList ?? this.communityList, filteredCommunity: filteredCommunity ?? this.filteredCommunity, @@ -38,7 +41,8 @@ class SpaceTreeState extends Equatable { selectedCommunities: selectedCommunities ?? this.selectedCommunities, selectedSpaces: selectedSpaces ?? this.selectedSpaces, soldCheck: soldCheck ?? this.soldCheck, - isSearching: isSearching ?? this.isSearching); + isSearching: isSearching ?? this.isSearching, + selectedCommunityAndSpaces: selectedCommunityAndSpaces ?? this.selectedCommunityAndSpaces); } @override @@ -50,7 +54,8 @@ class SpaceTreeState extends Equatable { selectedCommunities, selectedSpaces, soldCheck, - isSearching + isSearching, + selectedCommunityAndSpaces ]; } diff --git a/lib/pages/space_tree/view/space_tree_view.dart b/lib/pages/space_tree/view/space_tree_view.dart index f5dfbc84..1fcc63b0 100644 --- a/lib/pages/space_tree/view/space_tree_view.dart +++ b/lib/pages/space_tree/view/space_tree_view.dart @@ -11,7 +11,8 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/style.dart'; class SpaceTreeView extends StatelessWidget { - const SpaceTreeView({super.key}); + final Function onSelect; + const SpaceTreeView({required this.onSelect, super.key}); @override Widget build(BuildContext context) { @@ -64,6 +65,8 @@ class SpaceTreeView extends StatelessWidget { onItemSelected: () { context.read().add( OnCommunitySelected(community.uuid, community.spaces)); + + onSelect(); }, children: community.spaces.map((space) { return CustomExpansionTileSpaceTree( @@ -76,6 +79,7 @@ class SpaceTreeView extends StatelessWidget { onExpansionChanged: () { context.read().add( OnSpaceExpanded(community.uuid, space.uuid ?? '')); + onSelect(); }, isSelected: state.selectedSpaces.contains(space.uuid) || state.soldCheck.contains(space.uuid), @@ -109,6 +113,7 @@ class SpaceTreeView extends StatelessWidget { context .read() .add(OnSpaceSelected(communityId, child.uuid ?? '', child.children)); + onSelect(); }, onExpansionChanged: () { context.read().add(OnSpaceExpanded(communityId, child.uuid ?? '')); From 72241cba6cf2bce8040e9cc55e99a80706ac3af4 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 3 Feb 2025 14:47:05 +0400 Subject: [PATCH 161/175] added error validation --- .../assign_tag/bloc/assign_tag_bloc.dart | 14 ++++++++++---- .../assign_tag/bloc/assign_tag_state.dart | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart index 2d9222a6..3de906d3 100644 --- a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart @@ -57,6 +57,7 @@ class AssignTagBloc extends Bloc { if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { final tags = List.from(currentState.tags); tags[event.index].tag = event.tag; + emit(AssignTagLoaded( tags: tags, isSaveEnabled: _validateTags(tags), @@ -78,6 +79,7 @@ class AssignTagBloc extends Bloc { emit(AssignTagLoaded( tags: tags, isSaveEnabled: _validateTags(tags), + errorMessage: _getValidationError(tags), )); } }); @@ -106,12 +108,13 @@ class AssignTagBloc extends Bloc { emit(AssignTagLoaded( tags: updatedTags, isSaveEnabled: _validateTags(updatedTags), + errorMessage: _getValidationError(updatedTags), )); } else { emit(const AssignTagLoaded( - tags: [], - isSaveEnabled: false, - )); + tags: [], + isSaveEnabled: false, + errorMessage: 'Failed to delete tag')); } }); } @@ -125,7 +128,10 @@ class AssignTagBloc extends Bloc { String? _getValidationError(List tags) { final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); - if (hasEmptyTag) return 'Tags cannot be empty.'; + if (hasEmptyTag) { + return 'Tags cannot be empty.'; + } + final duplicateTags = tags .map((tag) => tag.tag?.trim() ?? '') .fold>({}, (map, tag) { diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart index 19cf4435..1ae0ea90 100644 --- a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart @@ -21,11 +21,11 @@ class AssignTagLoaded extends AssignTagState { const AssignTagLoaded({ required this.tags, required this.isSaveEnabled, - this.errorMessage, + required this.errorMessage, }); @override - List get props => [tags, isSaveEnabled]; + List get props => [tags, isSaveEnabled, errorMessage ?? '']; } class AssignTagError extends AssignTagState { From 6f51c2d2b6f5ea8c502f08e4647094893cd6c3a3 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 3 Feb 2025 22:23:53 +0400 Subject: [PATCH 162/175] provide all tags on edit space --- .../widgets/community_structure_widget.dart | 64 ++++--------------- .../widgets/dialogs/create_space_dialog.dart | 3 + .../helper/connection_helper.dart | 21 ++++++ .../helper/space_helper.dart | 18 ++++++ .../spaces_management/helper/tag_helper.dart | 24 +++++++ 5 files changed, 78 insertions(+), 52 deletions(-) create mode 100644 lib/pages/spaces_management/helper/connection_helper.dart diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 795a140f..165ddf34 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -22,7 +22,9 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/curved_li import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_card_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_container_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/helper/connection_helper.dart'; import 'package:syncrow_web/pages/spaces_management/helper/space_helper.dart'; +import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -131,7 +133,7 @@ class _CommunityStructureAreaState extends State { communities: widget.communities, communityName: widget.selectedCommunity?.name, community: widget.selectedCommunity, - isSave: isSave(spaces), + isSave: SpaceHelper.isSave(spaces), isEditingName: isEditingName, nameController: _nameController, onSave: _saveSpaces, @@ -176,7 +178,8 @@ class _CommunityStructureAreaState extends State { children: [ for (var connection in connections) Opacity( - opacity: _isHighlightedConnection(connection) + opacity: ConnectionHelper.isHighlightedConnection( + connection, widget.selectedSpace) ? 1.0 : 0.3, // Adjust opacity child: CustomPaint( @@ -209,7 +212,8 @@ class _CommunityStructureAreaState extends State { }, buildSpaceContainer: (int index) { final bool isHighlighted = - _isHighlightedSpace(spaces[index]); + SpaceHelper.isHighlightedSpace( + spaces[index], widget.selectedSpace); return Opacity( opacity: isHighlighted ? 1.0 : 0.3, @@ -295,7 +299,8 @@ class _CommunityStructureAreaState extends State { return CreateSpaceDialog( products: widget.products, spaceModels: widget.spaceModels, - allTags: _getAllTagValues(spaces), + allTags: + TagHelper.getAllTagValues(widget.communities, widget.spaceModels), parentSpace: parentIndex != null ? spaces[parentIndex] : null, onCreateSpace: (String name, String icon, @@ -306,7 +311,7 @@ class _CommunityStructureAreaState extends State { setState(() { // Set the first space in the center or use passed position Offset centerPosition = - position ?? _getCenterPosition(screenSize); + position ?? ConnectionHelper.getCenterPosition(screenSize); SpaceModel newSpace = SpaceModel( name: name, icon: icon, @@ -358,7 +363,8 @@ class _CommunityStructureAreaState extends State { tags: widget.selectedSpace?.tags, subspaces: widget.selectedSpace?.subspaces, isEdit: true, - allTags: _getAllTagValues(spaces), + allTags: TagHelper.getAllTagValues( + widget.communities, widget.spaceModels), onCreateSpace: (String name, String icon, List selectedProducts, @@ -527,17 +533,6 @@ class _CommunityStructureAreaState extends State { ); } - bool _isHighlightedSpace(SpaceModel space) { - final selectedSpace = widget.selectedSpace; - if (selectedSpace == null) return true; - - return space == selectedSpace || - selectedSpace.parent?.internalId == space.internalId || - selectedSpace.children - ?.any((child) => child.internalId == space.internalId) == - true; - } - void _deselectSpace(BuildContext context) { context.read().add( SelectSpaceEvent( @@ -545,28 +540,6 @@ class _CommunityStructureAreaState extends State { ); } - bool _isHighlightedConnection(Connection connection) { - if (widget.selectedSpace == null) return true; - - return connection.startSpace == widget.selectedSpace || - connection.endSpace == widget.selectedSpace; - } - - Offset _getCenterPosition(Size screenSize) { - return Offset( - screenSize.width / 2 - 260, - screenSize.height / 2 - 200, - ); - } - - bool isSave(List spaces) { - return spaces.isNotEmpty && - spaces.any((space) => - space.status == SpaceStatus.newSpace || - space.status == SpaceStatus.modified || - space.status == SpaceStatus.deleted); - } - void _onDuplicate(BuildContext parentContext) { final screenWidth = MediaQuery.of(context).size.width; @@ -652,8 +625,6 @@ class _CommunityStructureAreaState extends State { const double horizontalGap = 200.0; const double verticalGap = 100.0; - final Map nameCounters = {}; - SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition, SpaceModel? duplicatedParent) { Offset newPosition = parentPosition + Offset(horizontalGap, 0); @@ -729,10 +700,8 @@ class _CommunityStructureAreaState extends State { child.incomingConnection?.direction == "down" ?? false; if (isDownDirection && childrenWithDownDirection.length == 1) { - // Place the only "down" child vertically aligned with the parent childStartPosition = duplicated.position + Offset(0, verticalGap); } else if (!isDownDirection) { - // Position children with other directions horizontally childStartPosition = duplicated.position + Offset(horizontalGap, 0); } @@ -754,13 +723,4 @@ class _CommunityStructureAreaState extends State { } } - List _getAllTagValues(List spaces) { - final List allTags = []; - for (final space in spaces) { - if (space.tags != null) { - allTags.addAll(space.listAllTagValues()); - } - } - return allTags; - } } diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index a435a8fc..4e0bc317 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -461,6 +461,7 @@ class CreateSpaceDialogState extends State { builder: (context) => AssignTagDialog( products: widget.products, subspaces: subspaces, + allTags: widget.allTags, addedProducts: TagHelper .createInitialSelectedProductsForTags( tags ?? [], subspaces), @@ -660,6 +661,8 @@ class CreateSpaceDialogState extends State { void _showTagCreateDialog(BuildContext context, String name, bool isEdit, List? products) { + + print("ada ${widget.allTags}"); isEdit ? showDialog( context: context, diff --git a/lib/pages/spaces_management/helper/connection_helper.dart b/lib/pages/spaces_management/helper/connection_helper.dart new file mode 100644 index 00000000..7de8d80b --- /dev/null +++ b/lib/pages/spaces_management/helper/connection_helper.dart @@ -0,0 +1,21 @@ +import 'dart:ui'; + +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; + +class ConnectionHelper { + static Offset getCenterPosition(Size screenSize) { + return Offset( + screenSize.width / 2 - 260, + screenSize.height / 2 - 200, + ); + } + + static bool isHighlightedConnection( + Connection connection, SpaceModel? selectedSpace) { + if (selectedSpace == null) return true; + + return connection.startSpace == selectedSpace || + connection.endSpace == selectedSpace; + } +} diff --git a/lib/pages/spaces_management/helper/space_helper.dart b/lib/pages/spaces_management/helper/space_helper.dart index 75aebce9..c316106e 100644 --- a/lib/pages/spaces_management/helper/space_helper.dart +++ b/lib/pages/spaces_management/helper/space_helper.dart @@ -40,4 +40,22 @@ class SpaceHelper { return "$baseName(${maxNumber + 1})"; } + + static bool isSave(List spaces) { + return spaces.isNotEmpty && + spaces.any((space) => + space.status == SpaceStatus.newSpace || + space.status == SpaceStatus.modified || + space.status == SpaceStatus.deleted); + } + + static bool isHighlightedSpace(SpaceModel space, SpaceModel? selectedSpace) { + if (selectedSpace == null) return true; + + return space == selectedSpace || + selectedSpace.parent?.internalId == space.internalId || + selectedSpace.children + ?.any((child) => child.internalId == space.internalId) == + true; + } } diff --git a/lib/pages/spaces_management/helper/tag_helper.dart b/lib/pages/spaces_management/helper/tag_helper.dart index 757f46e1..9c0299ba 100644 --- a/lib/pages/spaces_management/helper/tag_helper.dart +++ b/lib/pages/spaces_management/helper/tag_helper.dart @@ -1,8 +1,11 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.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/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; @@ -337,4 +340,25 @@ class TagHelper { checkTagExistInSubspace: checkTagExistInSubspace, ); } + + static List getAllTagValues( + List communities, List? spaceModels) { + final Set allTags = {}; + + if (spaceModels != null) { + for (var model in spaceModels) { + allTags.addAll(model.listAllTagValues()); + } + } + + for (final community in communities) { + for (final space in community.spaces) { + if (space.tags != null) { + allTags.addAll(space.listAllTagValues()); + } + } + } + + return allTags.toList(); + } } From 5e5f127a4b88ddb44a758723fba54028b5625be7 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 4 Feb 2025 00:12:20 +0400 Subject: [PATCH 163/175] duplicate should be in same vertical offset --- .../all_spaces/widgets/community_structure_widget.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 165ddf34..c83c9ca9 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -627,7 +627,8 @@ class _CommunityStructureAreaState extends State { SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition, SpaceModel? duplicatedParent) { - Offset newPosition = parentPosition + Offset(horizontalGap, 0); + Offset newPosition = + Offset(parentPosition.dx + horizontalGap, original.position.dy); while (spaces.any((s) => (s.position - newPosition).distance < horizontalGap && @@ -661,7 +662,7 @@ class _CommunityStructureAreaState extends State { final newConnection = Connection( startSpace: duplicatedParent, endSpace: duplicated, - direction: "down", + direction: original.incomingConnection?.direction ?? 'down', ); connections.add(newConnection); duplicated.incomingConnection = newConnection; @@ -722,5 +723,4 @@ class _CommunityStructureAreaState extends State { duplicateRecursive(space, space.position, duplicatedParent); } } - } From 572520eed57631caa30483a60f164a5e84c22b9f Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Tue, 4 Feb 2025 01:54:18 +0300 Subject: [PATCH 164/175] Fixed issues --- .../device_mgmt_bloc/device_managment_bloc.dart | 3 ++- .../routines/bloc/routine_bloc/routine_bloc.dart | 2 +- lib/pages/space_tree/bloc/space_tree_bloc.dart | 16 ++++++++++++---- lib/pages/space_tree/view/space_tree_view.dart | 2 +- lib/services/devices_mang_api.dart | 3 ++- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart index 6df5a780..56b08ced 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart @@ -35,6 +35,7 @@ class DeviceManagementBloc extends Bloc devices = []; + _devices.clear(); var spaceBloc = event.context.read(); if (spaceBloc.state.selectedCommunities.isEmpty) { devices = await DevicesManagementApi().fetchDevices('', ''); @@ -42,7 +43,7 @@ class DeviceManagementBloc extends Bloc spacesList = spaceBloc.state.selectedCommunityAndSpaces[community] ?? []; for (var space in spacesList) { - await DevicesManagementApi().fetchDevices(community, space); + devices.addAll(await DevicesManagementApi().fetchDevices(community, space)); } } } diff --git a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart index bc83c40f..b05229c4 100644 --- a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart @@ -816,7 +816,7 @@ class RoutineBloc extends Bloc { FutureOr _fetchDevices(FetchDevicesInRoutine event, Emitter emit) async { emit(state.copyWith(isLoading: true)); try { - final devices = await DevicesManagementApi().fetchDevices(communityId, spaceId); + final devices = await DevicesManagementApi().fetchDevices('', ''); emit(state.copyWith(isLoading: false, devices: devices)); } catch (e) { diff --git a/lib/pages/space_tree/bloc/space_tree_bloc.dart b/lib/pages/space_tree/bloc/space_tree_bloc.dart index 75aa58ce..6ac0c0d9 100644 --- a/lib/pages/space_tree/bloc/space_tree_bloc.dart +++ b/lib/pages/space_tree/bloc/space_tree_bloc.dart @@ -6,8 +6,8 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model import 'package:syncrow_web/services/space_mana_api.dart'; class SpaceTreeBloc extends Bloc { - String selectedCommunityId = '9cb9da1f-adbe-4688-bf55-29e7584007a7'; - String selectedSpaceId = '68dd94cf-0240-4ccf-8c26-df506246a0dd'; + String selectedCommunityId = 'aff21a57-2f91-4e5c-b99b-0182c3ab65a9'; + String selectedSpaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6'; SpaceTreeBloc() : super(const SpaceTreeState()) { on(_fetchSpaces); @@ -87,6 +87,7 @@ class SpaceTreeBloc extends Bloc { List.from(state.selectedCommunities.toSet().toList()); List updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList()); List updatedSoldChecks = List.from(state.soldCheck.toSet().toList()); + Map> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces); List childrenIds = _getAllChildIds(event.children); @@ -101,10 +102,13 @@ class SpaceTreeBloc extends Bloc { updatedSoldChecks.removeWhere(childrenIds.contains); } + communityAndSpaces[event.communityId] = updatedSelectedSpaces; + emit(state.copyWith( selectedCommunities: updatedSelectedCommunities, selectedSpaces: updatedSelectedSpaces, - soldCheck: updatedSoldChecks)); + soldCheck: updatedSoldChecks, + selectedCommunityAndSpaces: communityAndSpaces)); } catch (e) { emit(const SpaceTreeErrorState('Something went wrong')); } @@ -116,6 +120,7 @@ class SpaceTreeBloc extends Bloc { List.from(state.selectedCommunities.toSet().toList()); List updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList()); List updatedSoldChecks = List.from(state.soldCheck.toSet().toList()); + Map> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces); List childrenIds = _getAllChildIds(event.children); bool isChildSelected = false; @@ -166,10 +171,13 @@ class SpaceTreeBloc extends Bloc { } } + communityAndSpaces[event.communityId] = updatedSelectedSpaces; + emit(state.copyWith( selectedCommunities: updatedSelectedCommunities, selectedSpaces: updatedSelectedSpaces, - soldCheck: updatedSoldChecks)); + soldCheck: updatedSoldChecks, + selectedCommunityAndSpaces: communityAndSpaces)); emit(state.copyWith(selectedSpaces: updatedSelectedSpaces)); } catch (e) { emit(const SpaceTreeErrorState('Something went wrong')); diff --git a/lib/pages/space_tree/view/space_tree_view.dart b/lib/pages/space_tree/view/space_tree_view.dart index 1fcc63b0..6f7dcb90 100644 --- a/lib/pages/space_tree/view/space_tree_view.dart +++ b/lib/pages/space_tree/view/space_tree_view.dart @@ -75,11 +75,11 @@ class SpaceTreeView extends StatelessWidget { onItemSelected: () { context.read().add(OnSpaceSelected( community.uuid, space.uuid ?? '', space.children)); + onSelect(); }, onExpansionChanged: () { context.read().add( OnSpaceExpanded(community.uuid, space.uuid ?? '')); - onSelect(); }, isSelected: state.selectedSpaces.contains(space.uuid) || state.soldCheck.contains(space.uuid), diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index cdddbbeb..1a8b979e 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -23,7 +23,8 @@ class DevicesManagementApi { : ApiEndpoints.getAllDevices, showServerMessage: true, expectedResponseModel: (json) { - List jsonData = json; + List jsonData = + communityId.isNotEmpty && spaceId.isNotEmpty ? json['data'] : json; List devicesList = jsonData.map((jsonItem) { return AllDevicesModel.fromJson(jsonItem); }).toList(); From d1d570b40f338725921e5a95c02e50bce0adcda8 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 4 Feb 2025 11:36:26 +0400 Subject: [PATCH 165/175] Fixed issue in loading space model --- .../all_spaces/bloc/space_management_bloc.dart | 4 +++- .../all_spaces/view/spaces_management_page.dart | 1 + .../widgets/dialogs/create_space_dialog.dart | 12 ++++++++---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 31e19af4..a525334d 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -87,6 +87,7 @@ class SpaceManagementBloc prevSpaceModels = List.from( (previousState as dynamic).spaceModels ?? [], ); + allSpaces.addAll(prevSpaceModels); } if (prevSpaceModels.isEmpty) { @@ -273,6 +274,8 @@ class SpaceManagementBloc await _api.createCommunity(event.name, event.description); var prevSpaceModels = await fetchSpaceModels(previousState); + print("space models are ${prevSpaceModels}"); + if (newCommunity != null) { if (previousState is SpaceManagementLoaded || previousState is BlankState) { @@ -314,7 +317,6 @@ class SpaceManagementBloc SelectSpaceEvent event, Emitter emit, ) { - _handleCommunitySpaceStateUpdate( emit: emit, selectedCommunity: event.selectedCommunity, diff --git a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart index e601cca4..94e153f1 100644 --- a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart +++ b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart @@ -43,6 +43,7 @@ class SpaceManagementPageState extends State { rightBody: const NavigateHomeGridView(), scaffoldBody: BlocBuilder( builder: (context, state) { + print("current state is ${state}"); if (state is SpaceManagementLoading) { return const Center(child: CircularProgressIndicator()); } else if (state is BlankState) { diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 4e0bc317..a3e19fce 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -83,8 +83,13 @@ class CreateSpaceDialogState extends State { widget.selectedProducts.isNotEmpty ? widget.selectedProducts : []; isOkButtonEnabled = enteredName.isNotEmpty || nameController.text.isNotEmpty; - tags = widget.tags ?? []; - subspaces = widget.subspaces ?? []; + if (widget.currentSpaceModel != null) { + subspaces = []; + tags = []; + } else { + tags = widget.tags ?? []; + subspaces = widget.subspaces ?? []; + } selectedSpaceModel = widget.currentSpaceModel; } @@ -661,8 +666,7 @@ class CreateSpaceDialogState extends State { void _showTagCreateDialog(BuildContext context, String name, bool isEdit, List? products) { - - print("ada ${widget.allTags}"); + print("ada ${widget.allTags}"); isEdit ? showDialog( context: context, From c5c5088724d6f0e24fb72f6851a2d689ce96ff44 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 4 Feb 2025 11:38:11 +0400 Subject: [PATCH 166/175] removed logs --- .../all_spaces/bloc/space_management_bloc.dart | 2 -- .../all_spaces/view/spaces_management_page.dart | 11 +++++------ .../widgets/dialogs/create_space_dialog.dart | 1 - 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index a525334d..ee6018e6 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -274,8 +274,6 @@ class SpaceManagementBloc await _api.createCommunity(event.name, event.description); var prevSpaceModels = await fetchSpaceModels(previousState); - print("space models are ${prevSpaceModels}"); - if (newCommunity != null) { if (previousState is SpaceManagementLoaded || previousState is BlankState) { diff --git a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart index 94e153f1..3abab9cc 100644 --- a/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart +++ b/lib/pages/spaces_management/all_spaces/view/spaces_management_page.dart @@ -43,7 +43,6 @@ class SpaceManagementPageState extends State { rightBody: const NavigateHomeGridView(), scaffoldBody: BlocBuilder( builder: (context, state) { - print("current state is ${state}"); if (state is SpaceManagementLoading) { return const Center(child: CircularProgressIndicator()); } else if (state is BlankState) { @@ -65,11 +64,11 @@ class SpaceManagementPageState extends State { ); } else if (state is SpaceModelLoaded) { return LoadedSpaceView( - communities: state.communities, - products: state.products, - spaceModels: state.spaceModels, - shouldNavigateToSpaceModelPage: true, - ); + communities: state.communities, + products: state.products, + spaceModels: state.spaceModels, + shouldNavigateToSpaceModelPage: true, + ); } else if (state is SpaceManagementError) { return Center(child: Text('Error: ${state.errorMessage}')); } diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index a3e19fce..cd5ce4a5 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -666,7 +666,6 @@ class CreateSpaceDialogState extends State { void _showTagCreateDialog(BuildContext context, String name, bool isEdit, List? products) { - print("ada ${widget.allTags}"); isEdit ? showDialog( context: context, From 09e25641838344b7e9f2037a2aaa6818d0d1d7cb Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 4 Feb 2025 13:29:09 +0400 Subject: [PATCH 167/175] add disabled state to ButtonContentWidget, When disabled, the entire button becomes opaque --- .../widgets/button_content_widget.dart | 89 ++++++++++--------- 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/button_content_widget.dart b/lib/pages/spaces_management/space_model/widgets/button_content_widget.dart index a3ccad7c..2cd338e4 100644 --- a/lib/pages/spaces_management/space_model/widgets/button_content_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/button_content_widget.dart @@ -6,55 +6,64 @@ class ButtonContentWidget extends StatelessWidget { final IconData? icon; final String label; final String? svgAssets; + final bool disabled; - const ButtonContentWidget( - {Key? key, this.icon, required this.label, this.svgAssets}) - : super(key: key); + const ButtonContentWidget({ + Key? key, + this.icon, + required this.label, + this.svgAssets, + this.disabled = false, + }) : super(key: key); @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; - return SizedBox( - width: screenWidth * 0.25, - child: Container( - decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, - border: Border.all( - color: ColorsManager.neutralGray, - width: 3.0, + return Opacity( + opacity: disabled ? 0.5 : 1.0, + child: SizedBox( + width: screenWidth * 0.25, + child: Container( + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + border: Border.all( + color: ColorsManager.neutralGray, + width: 3.0, + ), + borderRadius: BorderRadius.circular(20), ), - borderRadius: BorderRadius.circular(20), - ), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), - child: Row( - children: [ - if (icon != null) - Icon( - icon, - color: ColorsManager.spaceColor, - ), - if (svgAssets != null) - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - svgAssets!, - width: screenWidth * 0.015, // Adjust icon size - height: screenWidth * 0.015, + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), + child: Row( + children: [ + if (icon != null) + Icon( + icon, + color: ColorsManager.spaceColor, + ), + if (svgAssets != null) + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + svgAssets!, + width: screenWidth * 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + label, + style: const TextStyle( + color: ColorsManager.blackColor, + fontSize: 16, + ), ), ), - const SizedBox(width: 10), - Expanded( - child: Text( - label, - style: const TextStyle( - color: ColorsManager.blackColor, - fontSize: 16, - ), - ), - ), - ], + ], + ), ), ), ), From 1fa33a271fd62846f0fd584fa64c8f27a4cc0017 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 4 Feb 2025 13:46:11 +0400 Subject: [PATCH 168/175] added disabled space model button on adding tags and subspace, vice versa --- .../widgets/dialogs/create_space_dialog.dart | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index cd5ce4a5..59b870e5 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -95,6 +95,10 @@ class CreateSpaceDialogState extends State { @override Widget build(BuildContext context) { + bool isSpaceModelDisabled = (tags != null && tags!.isNotEmpty || + subspaces != null && subspaces!.isNotEmpty); + bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null); + final screenWidth = MediaQuery.of(context).size.width; return AlertDialog( title: widget.isEdit @@ -240,11 +244,14 @@ class CreateSpaceDialogState extends State { padding: EdgeInsets.zero, ), onPressed: () { - _showLinkSpaceModelDialog(context); + isSpaceModelDisabled + ? null + : _showLinkSpaceModelDialog(context); }, - child: const ButtonContentWidget( + child: ButtonContentWidget( svgAssets: Assets.link, label: 'Link a space model', + disabled: isSpaceModelDisabled, ), ) : Container( @@ -333,12 +340,15 @@ class CreateSpaceDialogState extends State { overlayColor: ColorsManager.transparentColor, ), onPressed: () async { - _showSubSpaceDialog(context, enteredName, [], - false, widget.products, subspaces); + isTagsAndSubspaceModelDisabled + ? null + : _showSubSpaceDialog(context, enteredName, + [], false, widget.products, subspaces); }, - child: const ButtonContentWidget( + child: ButtonContentWidget( icon: Icons.add, label: 'Create Sub Space', + disabled: isTagsAndSubspaceModelDisabled, ), ) : SizedBox( @@ -492,19 +502,22 @@ class CreateSpaceDialogState extends State { ) : TextButton( onPressed: () { - _showTagCreateDialog( - context, - enteredName, - widget.isEdit, - widget.products, - ); + isTagsAndSubspaceModelDisabled + ? null + : _showTagCreateDialog( + context, + enteredName, + widget.isEdit, + widget.products, + ); }, style: TextButton.styleFrom( padding: EdgeInsets.zero, ), - child: const ButtonContentWidget( + child: ButtonContentWidget( icon: Icons.add, label: 'Add Devices', + disabled: isTagsAndSubspaceModelDisabled, )) ], ), From d5fcbe2601dbbd6de04d9da0167629c58be5bfe0 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 4 Feb 2025 14:15:39 +0400 Subject: [PATCH 169/175] added edit space sibling conflict --- .../widgets/dialogs/create_space_dialog.dart | 24 ++------------ .../helper/space_helper.dart | 33 +++++++++++++++++++ 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 59b870e5..f3df1637 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -12,6 +12,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/helper/space_helper.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; @@ -177,7 +178,7 @@ class CreateSpaceDialogState extends State { isNameFieldInvalid = value.isEmpty; if (!isNameFieldInvalid) { - if (_isNameConflict(value)) { + if (SpaceHelper.isNameConflict(value, widget.parentSpace, widget.editSpace)) { isNameFieldExist = true; isOkButtonEnabled = false; } else { @@ -591,26 +592,7 @@ class CreateSpaceDialogState extends State { ); } - bool _isNameConflict(String value) { - final parentSpace = widget.parentSpace; - final editSpace = widget.editSpace; - final siblings = parentSpace?.children - .where((child) => child.uuid != editSpace?.uuid) - .toList() ?? - []; - final siblingConflict = siblings.any((child) => child.name == value); - final parentConflict = - parentSpace?.name == value && parentSpace?.uuid != editSpace?.uuid; - final parentOfEditSpaceConflict = editSpace?.parent?.name == value && - editSpace?.parent?.uuid != editSpace?.uuid; - final childConflict = - editSpace?.children.any((child) => child.name == value) ?? false; - return siblingConflict || - parentConflict || - parentOfEditSpaceConflict || - childConflict; - } - + void _showLinkSpaceModelDialog(BuildContext context) { showDialog( context: context, diff --git a/lib/pages/spaces_management/helper/space_helper.dart b/lib/pages/spaces_management/helper/space_helper.dart index c316106e..126306cd 100644 --- a/lib/pages/spaces_management/helper/space_helper.dart +++ b/lib/pages/spaces_management/helper/space_helper.dart @@ -58,4 +58,37 @@ class SpaceHelper { ?.any((child) => child.internalId == space.internalId) == true; } + + static bool isNameConflict( + String value, SpaceModel? parentSpace, SpaceModel? editSpace) { + final siblings = parentSpace?.children + .where((child) => child.internalId != editSpace?.internalId) + .toList() ?? + []; + + final editSiblings = editSpace?.parent?.children + .where((child) => child.internalId != editSpace.internalId) + .toList() ?? + []; + + final editSiblingConflict = + editSiblings.any((child) => child.name == value); + + final siblingConflict = siblings.any((child) => child.name == value); + + final parentConflict = parentSpace?.name == value && + parentSpace?.internalId != editSpace?.internalId; + + final parentOfEditSpaceConflict = editSpace?.parent?.name == value && + editSpace?.parent?.internalId != editSpace?.internalId; + + final childConflict = + editSpace?.children.any((child) => child.name == value) ?? false; + + return siblingConflict || + parentConflict || + editSiblingConflict || + parentOfEditSpaceConflict || + childConflict; + } } From c2b77ad1fcf3c7b7c827fa20fb7f21e1cb78ff74 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 5 Feb 2025 11:15:25 +0400 Subject: [PATCH 170/175] fixed issue on duplicate --- .../widgets/community_structure_widget.dart | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index c83c9ca9..f82bce56 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -639,6 +639,16 @@ class _CommunityStructureAreaState extends State { final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces); + final List? duplicatedSubspaces; + final List? duplicatedTags; + if (original.spaceModel != null) { + duplicatedTags = []; + duplicatedSubspaces = []; + } else { + duplicatedTags = original.tags; + duplicatedSubspaces = original.subspaces; + } + final duplicated = SpaceModel( name: duplicatedName, icon: original.icon, @@ -648,8 +658,8 @@ class _CommunityStructureAreaState extends State { status: SpaceStatus.newSpace, parent: duplicatedParent, spaceModel: original.spaceModel, - subspaces: original.subspaces, - tags: original.tags, + subspaces: duplicatedSubspaces, + tags: duplicatedTags, ); originalToDuplicate[original] = duplicated; From af4c0f84cbbf0f3dfec243f42f4b3e229c97f09e Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 5 Feb 2025 11:15:38 +0400 Subject: [PATCH 171/175] fixed assign tag issue --- .../assign_tag/views/assign_tag_dialog.dart | 13 +++++++++---- .../views/assign_tag_models_dialog.dart | 10 ++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart index 0e08de53..9e2fee32 100644 --- a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart +++ b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart @@ -71,6 +71,7 @@ class AssignTagDialog extends StatelessWidget { child: DataTable( headingRowColor: WidgetStateProperty.all( ColorsManager.dataHeaderGrey), + key: ValueKey(state.tags.length), border: TableBorder.all( color: ColorsManager.dataHeaderGrey, width: 1, @@ -120,6 +121,7 @@ class AssignTagDialog extends StatelessWidget { final controller = controllers[index]; final availableTags = getAvailableTags( allTags ?? [], state.tags, tag); + return DataRow( cells: [ DataCell(Text((index + 1).toString())), @@ -158,6 +160,8 @@ class AssignTagDialog extends StatelessWidget { .add(DeleteTag( tagToDelete: tag, tags: state.tags)); + + controllers.removeAt(index); }, tooltip: 'Delete Tag', padding: EdgeInsets.zero, @@ -255,6 +259,7 @@ class AssignTagDialog extends StatelessWidget { spaceTags: processedTags, isCreate: false, onSave: onSave, + allTags: allTags, ), ); }, @@ -265,10 +270,10 @@ class AssignTagDialog extends StatelessWidget { Expanded( child: DefaultButton( borderRadius: 10, - backgroundColor: state.isSaveEnabled - ? ColorsManager.secondaryColor - : ColorsManager.grayColor, - foregroundColor: ColorsManager.whiteColors, + backgroundColor: ColorsManager.secondaryColor, + foregroundColor: state.isSaveEnabled + ? ColorsManager.whiteColors + : ColorsManager.whiteColorsWithOpacity, onPressed: state.isSaveEnabled ? () async { final updatedTags = List.from(state.tags); diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 5fdaf937..3e302d2d 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -82,6 +82,7 @@ class AssignTagModelsDialog extends StatelessWidget { child: DataTable( headingRowColor: WidgetStateProperty.all( ColorsManager.dataHeaderGrey), + key: ValueKey(state.tags.length), border: TableBorder.all( color: ColorsManager.dataHeaderGrey, width: 1, @@ -176,6 +177,7 @@ class AssignTagModelsDialog extends StatelessWidget { .add(DeleteTagModel( tagToDelete: tag, tags: state.tags)); + controllers.removeAt(index); }, tooltip: 'Delete Tag', padding: EdgeInsets.zero, @@ -302,10 +304,10 @@ class AssignTagModelsDialog extends StatelessWidget { Expanded( child: DefaultButton( borderRadius: 10, - backgroundColor: state.isSaveEnabled - ? ColorsManager.secondaryColor - : ColorsManager.grayColor, - foregroundColor: ColorsManager.whiteColors, + backgroundColor: ColorsManager.secondaryColor, + foregroundColor: state.isSaveEnabled + ? ColorsManager.whiteColors + : ColorsManager.whiteColorsWithOpacity, onPressed: state.isSaveEnabled ? () async { final updatedTags = From 132cafcaa29ae7489f5e5c07e628eca0a297f8c7 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Wed, 5 Feb 2025 11:52:44 +0300 Subject: [PATCH 172/175] Enhanced the side tree design --- .../widgets/device_managment_body.dart | 2 +- lib/pages/routines/view/routines_view.dart | 99 ++++---- .../space_tree/view/space_tree_view.dart | 217 +++++++++++++----- 3 files changed, 202 insertions(+), 116 deletions(-) diff --git a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart index 11c692fe..52b2321c 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart @@ -69,7 +69,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { }, )), Expanded( - flex: 3, + flex: 4, child: state is DeviceManagementLoading ? const Center(child: CircularProgressIndicator()) : Column( diff --git a/lib/pages/routines/view/routines_view.dart b/lib/pages/routines/view/routines_view.dart index 2f7daff8..43d3b227 100644 --- a/lib/pages/routines/view/routines_view.dart +++ b/lib/pages/routines/view/routines_view.dart @@ -33,64 +33,55 @@ class _RoutinesViewState extends State { return Row( children: [ Expanded( - child: - // SideSpacesView( - // onSelectAction: (String communityId, String spaceId) { - // // context.read() - // // ..add(LoadScenes(spaceId, communityId)) - // // ..add(LoadAutomation(spaceId)); - // }, - // ) - SpaceTreeView( + child: SpaceTreeView( onSelect: () {}, )), Expanded( - flex: 3, - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Create New Routines", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox( - height: 10, - ), - RoutineViewCard( - onTap: () { - if (context.read().selectedCommunityId.isNotEmpty && - context.read().selectedSpaceId.isNotEmpty) { - context.read().add( - (ResetRoutineState()), - ); - BlocProvider.of(context).add( - const CreateNewRoutineViewEvent(createRoutineView: true), - ); - } else { - CustomSnackBar.redSnackBar('Please select a space'); - } - }, - icon: Icons.add, - textString: '', - ), - const SizedBox( - height: 15, - ), - const Expanded(child: FetchRoutineScenesAutomation()), - ], - ), - ], + flex: 4, + child: ListView(children: [ + Container( + padding: const EdgeInsets.all(16), + height: MediaQuery.sizeOf(context).height, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Create New Routines", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox( + height: 10, + ), + RoutineViewCard( + onTap: () { + if (context.read().selectedCommunityId.isNotEmpty && + context.read().selectedSpaceId.isNotEmpty) { + context.read().add( + (ResetRoutineState()), + ); + BlocProvider.of(context).add( + const CreateNewRoutineViewEvent(createRoutineView: true), + ); + } else { + CustomSnackBar.redSnackBar('Please select a space'); + } + }, + icon: Icons.add, + textString: '', + ), + const SizedBox( + height: 15, + ), + const Expanded(child: FetchRoutineScenesAutomation()), + ], + ), ), - ), + ]), ), ], ); diff --git a/lib/pages/space_tree/view/space_tree_view.dart b/lib/pages/space_tree/view/space_tree_view.dart index 6f7dcb90..de9d088e 100644 --- a/lib/pages/space_tree/view/space_tree_view.dart +++ b/lib/pages/space_tree/view/space_tree_view.dart @@ -10,10 +10,23 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/style.dart'; -class SpaceTreeView extends StatelessWidget { +class SpaceTreeView extends StatefulWidget { final Function onSelect; const SpaceTreeView({required this.onSelect, super.key}); + @override + State createState() => _SpaceTreeViewState(); +} + +class _SpaceTreeViewState extends State { + final ScrollController _scrollController = ScrollController(); + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { @@ -21,7 +34,6 @@ class SpaceTreeView extends StatelessWidget { return Container( height: MediaQuery.sizeOf(context).height, decoration: subSectionContainerDecoration, - // padding: const EdgeInsets.all(16.0), child: state is SpaceTreeLoadingState ? const Center(child: CircularProgressIndicator()) : Column( @@ -33,67 +45,150 @@ class SpaceTreeView extends StatelessWidget { ), const SizedBox(height: 16), Expanded( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: list.isEmpty - ? Center( - child: Text( - 'No results found', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.lightGrayColor, // Gray when not selected - fontWeight: FontWeight.w400, + child: ListView( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + children: [ + Container( + width: MediaQuery.sizeOf(context).width * 0.5, + padding: const EdgeInsets.all(8.0), + child: list.isEmpty + ? Center( + child: Text( + 'No results found', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.lightGrayColor, + fontWeight: FontWeight.w400, + ), + ), + ) + : Scrollbar( + scrollbarOrientation: ScrollbarOrientation.left, + thumbVisibility: true, + controller: _scrollController, + child: Padding( + padding: const EdgeInsets.only(left: 16), + child: ListView( + controller: _scrollController, + shrinkWrap: true, + children: list + .map( + (community) => CustomExpansionTileSpaceTree( + title: community.name, + isSelected: state.selectedCommunities + .contains(community.uuid), + isSoldCheck: state.selectedCommunities + .contains(community.uuid), + onExpansionChanged: () { + context + .read() + .add(OnCommunityExpanded(community.uuid)); + }, + isExpanded: state.expandedCommunities + .contains(community.uuid), + onItemSelected: () { + context.read().add( + OnCommunitySelected( + community.uuid, community.spaces)); + widget.onSelect(); + }, + children: community.spaces.map((space) { + return CustomExpansionTileSpaceTree( + title: space.name, + isExpanded: + state.expandedSpaces.contains(space.uuid), + onItemSelected: () { + context.read().add( + OnSpaceSelected(community.uuid, + space.uuid ?? '', space.children)); + widget.onSelect(); + }, + onExpansionChanged: () { + 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), + children: _buildNestedSpaces( + context, state, space, community.uuid), + ); + }).toList(), + ), + ) + .toList(), ), - ), - ) - : ListView( - shrinkWrap: true, - children: list - .map( - (community) => CustomExpansionTileSpaceTree( - title: community.name, - isSelected: - state.selectedCommunities.contains(community.uuid), - isSoldCheck: - state.selectedCommunities.contains(community.uuid), - onExpansionChanged: () { - context - .read() - .add(OnCommunityExpanded(community.uuid)); - }, - isExpanded: - state.expandedCommunities.contains(community.uuid), - onItemSelected: () { - context.read().add( - OnCommunitySelected(community.uuid, community.spaces)); - - onSelect(); - }, - children: community.spaces.map((space) { - return CustomExpansionTileSpaceTree( - title: space.name, - isExpanded: state.expandedSpaces.contains(space.uuid), - onItemSelected: () { - context.read().add(OnSpaceSelected( - community.uuid, space.uuid ?? '', space.children)); - onSelect(); - }, - onExpansionChanged: () { - 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), - children: _buildNestedSpaces( - context, state, space, community.uuid), - ); - }).toList(), - ), - ) - .toList(), - ), + ), + ), + ), + ], ), ), + + // Expanded( + // child: Padding( + // padding: const EdgeInsets.all(8.0), + // child: list.isEmpty + // ? Center( + // child: Text( + // 'No results found', + // style: Theme.of(context).textTheme.bodySmall!.copyWith( + // color: ColorsManager.lightGrayColor, // Gray when not selected + // fontWeight: FontWeight.w400, + // ), + // ), + // ) + // : ListView( + // shrinkWrap: true, + // children: list + // .map( + // (community) => CustomExpansionTileSpaceTree( + // title: community.name, + // isSelected: + // state.selectedCommunities.contains(community.uuid), + // isSoldCheck: + // state.selectedCommunities.contains(community.uuid), + // onExpansionChanged: () { + // context + // .read() + // .add(OnCommunityExpanded(community.uuid)); + // }, + // isExpanded: + // state.expandedCommunities.contains(community.uuid), + // onItemSelected: () { + // context.read().add( + // OnCommunitySelected(community.uuid, community.spaces)); + + // onSelect(); + // }, + // children: community.spaces.map((space) { + // return CustomExpansionTileSpaceTree( + // title: space.name, + // isExpanded: state.expandedSpaces.contains(space.uuid), + // onItemSelected: () { + // context.read().add(OnSpaceSelected( + // community.uuid, space.uuid ?? '', space.children)); + // onSelect(); + // }, + // onExpansionChanged: () { + // 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), + // children: _buildNestedSpaces( + // context, state, space, community.uuid), + // ); + // }).toList(), + // ), + // ) + // .toList(), + // ), + // ), + // ), ], ), ); @@ -113,7 +208,7 @@ class SpaceTreeView extends StatelessWidget { context .read() .add(OnSpaceSelected(communityId, child.uuid ?? '', child.children)); - onSelect(); + widget.onSelect(); }, onExpansionChanged: () { context.read().add(OnSpaceExpanded(communityId, child.uuid ?? '')); From 962f2d686155f37f791db3987ca25a84097e9622 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 5 Feb 2025 16:00:53 +0400 Subject: [PATCH 173/175] Fixed tgag repeat --- .../assign_tag/bloc/assign_tag_bloc.dart | 77 ++++++++++++------- .../assign_tag/bloc/assign_tag_state.dart | 9 ++- .../assign_tag/views/assign_tag_dialog.dart | 20 ++--- .../bloc/assign_tag_model_bloc.dart | 71 +++++++++++------ .../bloc/assign_tag_model_state.dart | 5 +- .../views/assign_tag_models_dialog.dart | 10 +-- 6 files changed, 117 insertions(+), 75 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart index 3de906d3..a06e6977 100644 --- a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart @@ -4,7 +4,9 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_e import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart'; class AssignTagBloc extends Bloc { - AssignTagBloc() : super(AssignTagInitial()) { + final List allTags; + + AssignTagBloc(this.allTags) : super(AssignTagInitial()) { on((event, emit) { final initialTags = event.initialTags ?? []; @@ -16,25 +18,25 @@ class AssignTagBloc extends Bloc { } } - final allTags = []; + final tags = []; for (var selectedProduct in event.addedProducts) { final existingCount = existingTagCounts[selectedProduct.productId] ?? 0; if (selectedProduct.count == 0 || selectedProduct.count <= existingCount) { - allTags.addAll(initialTags + tags.addAll(initialTags .where((tag) => tag.product?.uuid == selectedProduct.productId)); continue; } final missingCount = selectedProduct.count - existingCount; - allTags.addAll(initialTags + tags.addAll(initialTags .where((tag) => tag.product?.uuid == selectedProduct.productId)); if (missingCount > 0) { - allTags.addAll(List.generate( + tags.addAll(List.generate( missingCount, (index) => Tag( tag: '', @@ -45,10 +47,14 @@ class AssignTagBloc extends Bloc { } } + final updatedTags = _calculateAvailableTags(allTags, tags); + emit(AssignTagLoaded( - tags: allTags, - isSaveEnabled: _validateTags(allTags), - errorMessage: '')); + tags: tags, + updatedTags: updatedTags, + isSaveEnabled: _validateTags(tags), + errorMessage: '', + )); }); on((event, emit) { @@ -56,10 +62,13 @@ class AssignTagBloc extends Bloc { if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { final tags = List.from(currentState.tags); - tags[event.index].tag = event.tag; + tags[event.index] = tags[event.index].copyWith(tag: event.tag); + + final updatedTags = _calculateAvailableTags(allTags, tags); emit(AssignTagLoaded( tags: tags, + updatedTags: updatedTags, isSaveEnabled: _validateTags(tags), errorMessage: _getValidationError(tags), )); @@ -72,12 +81,15 @@ class AssignTagBloc extends Bloc { if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { final tags = List.from(currentState.tags); - // Use copyWith for immutability + // Update the location tags[event.index] = tags[event.index].copyWith(location: event.location); + final updatedTags = _calculateAvailableTags(allTags, tags); + emit(AssignTagLoaded( tags: tags, + updatedTags: updatedTags, isSaveEnabled: _validateTags(tags), errorMessage: _getValidationError(tags), )); @@ -92,6 +104,7 @@ class AssignTagBloc extends Bloc { emit(AssignTagLoaded( tags: tags, + updatedTags: _calculateAvailableTags(allTags, tags), isSaveEnabled: _validateTags(tags), errorMessage: _getValidationError(tags), )); @@ -102,38 +115,37 @@ class AssignTagBloc extends Bloc { final currentState = state; if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { - final updatedTags = List.from(currentState.tags) + final tags = List.from(currentState.tags) ..remove(event.tagToDelete); + // Recalculate available tags + final updatedTags = _calculateAvailableTags(allTags, tags); + emit(AssignTagLoaded( - tags: updatedTags, - isSaveEnabled: _validateTags(updatedTags), - errorMessage: _getValidationError(updatedTags), + tags: tags, + updatedTags: updatedTags, + isSaveEnabled: _validateTags(tags), + errorMessage: _getValidationError(tags), )); - } else { - emit(const AssignTagLoaded( - tags: [], - isSaveEnabled: false, - errorMessage: 'Failed to delete tag')); } }); } + // Validate the tags for duplicates or empty values bool _validateTags(List tags) { final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); - final isValid = uniqueTags.length == tags.length && !hasEmptyTag; - return isValid; + return uniqueTags.length == tags.length && !hasEmptyTag; } + // Get validation error for duplicate tags String? _getValidationError(List tags) { - final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); - if (hasEmptyTag) { - return 'Tags cannot be empty.'; - } - - final duplicateTags = tags + final nonEmptyTags = tags .map((tag) => tag.tag?.trim() ?? '') + .where((tag) => tag.isNotEmpty) + .toList(); + + final duplicateTags = nonEmptyTags .fold>({}, (map, tag) { map[tag] = (map[tag] ?? 0) + 1; return map; @@ -149,4 +161,15 @@ class AssignTagBloc extends Bloc { return null; } + + List _calculateAvailableTags(List allTags, List tags) { + final selectedTags = tags + .where((tag) => (tag.tag?.trim().isNotEmpty ?? false)) + .map((tag) => tag.tag!.trim()) + .toSet(); + + final availableTags = + allTags.where((tag) => !selectedTags.contains(tag.trim())).toList(); + return availableTags; + } } diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart index 1ae0ea90..6a2dae4b 100644 --- a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart @@ -1,6 +1,5 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; abstract class AssignTagState extends Equatable { const AssignTagState(); @@ -15,17 +14,21 @@ class AssignTagLoading extends AssignTagState {} class AssignTagLoaded extends AssignTagState { final List tags; + final List updatedTags; + final bool isSaveEnabled; - final String? errorMessage; + final String? errorMessage; const AssignTagLoaded({ required this.tags, required this.isSaveEnabled, + required this.updatedTags, required this.errorMessage, }); @override - List get props => [tags, isSaveEnabled, errorMessage ?? '']; + List get props => + [tags, updatedTags, isSaveEnabled, errorMessage ?? '']; } class AssignTagError extends AssignTagState { diff --git a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart index 9e2fee32..3b794b61 100644 --- a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart +++ b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart @@ -14,6 +14,7 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_e import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:uuid/uuid.dart'; class AssignTagDialog extends StatelessWidget { final List? products; @@ -47,7 +48,7 @@ class AssignTagDialog extends StatelessWidget { ..add('Main Space'); return BlocProvider( - create: (_) => AssignTagBloc() + create: (_) => AssignTagBloc(allTags ?? []) ..add(InitializeTags( initialTags: initialTags, addedProducts: addedProducts, @@ -119,8 +120,6 @@ class AssignTagDialog extends StatelessWidget { : List.generate(state.tags.length, (index) { final tag = state.tags[index]; final controller = controllers[index]; - final availableTags = getAvailableTags( - allTags ?? [], state.tags, tag); return DataRow( cells: [ @@ -180,7 +179,9 @@ class AssignTagDialog extends StatelessWidget { width: double .infinity, // Ensure full width for dropdown child: DialogTextfieldDropdown( - items: availableTags, + key: ValueKey( + 'dropdown_${Uuid().v4()}_${index}'), + items: state.updatedTags, initialValue: tag.tag, onSelected: (value) { controller.text = value; @@ -306,15 +307,4 @@ class AssignTagDialog extends StatelessWidget { ), ); } - - List getAvailableTags( - List allTags, List currentTags, Tag currentTag) { - List availableTagsForTagModel = TagHelper.getAvailableTags( - allTags: allTags, - currentTags: currentTags, - currentTag: currentTag, - getTag: (tag) => tag.tag ?? '', - ); - return availableTagsForTagModel; - } } diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart index 92d7c0e1..d0e37f6a 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart @@ -5,7 +5,9 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model class AssignTagModelBloc extends Bloc { - AssignTagModelBloc() : super(AssignTagModelInitial()) { + final List allTags; + + AssignTagModelBloc(this.allTags) : super(AssignTagModelInitial()) { on((event, emit) { final initialTags = event.initialTags ?? []; @@ -17,25 +19,25 @@ class AssignTagModelBloc } } - final allTags = []; + final tags = []; for (var selectedProduct in event.addedProducts) { final existingCount = existingTagCounts[selectedProduct.productId] ?? 0; if (selectedProduct.count == 0 || selectedProduct.count <= existingCount) { - allTags.addAll(initialTags + tags.addAll(initialTags .where((tag) => tag.product?.uuid == selectedProduct.productId)); continue; } final missingCount = selectedProduct.count - existingCount; - allTags.addAll(initialTags + tags.addAll(initialTags .where((tag) => tag.product?.uuid == selectedProduct.productId)); if (missingCount > 0) { - allTags.addAll(List.generate( + tags.addAll(List.generate( missingCount, (index) => TagModel( tag: '', @@ -46,9 +48,12 @@ class AssignTagModelBloc } } + final updatedTags = _calculateAvailableTags(allTags, tags); + emit(AssignTagModelLoaded( - tags: allTags, - isSaveEnabled: _validateTags(allTags), + tags: tags, + updatedTags: updatedTags, + isSaveEnabled: _validateTags(tags), errorMessage: '')); }); @@ -57,9 +62,12 @@ class AssignTagModelBloc if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) { final tags = List.from(currentState.tags); - tags[event.index].tag = event.tag; + tags[event.index] = tags[event.index].copyWith(tag: event.tag); + final updatedTags = _calculateAvailableTags(allTags, tags); + emit(AssignTagModelLoaded( tags: tags, + updatedTags: updatedTags, isSaveEnabled: _validateTags(tags), errorMessage: _getValidationError(tags), )); @@ -77,9 +85,13 @@ class AssignTagModelBloc tags[event.index] = tags[event.index].copyWith(location: event.location); + final updatedTags = _calculateAvailableTags(allTags, tags); + emit(AssignTagModelLoaded( tags: tags, + updatedTags: updatedTags, isSaveEnabled: _validateTags(tags), + errorMessage: _getValidationError(tags), )); } }); @@ -93,6 +105,7 @@ class AssignTagModelBloc emit(AssignTagModelLoaded( tags: tags, + updatedTags: _calculateAvailableTags(allTags, tags), isSaveEnabled: _validateTags(tags), errorMessage: _getValidationError(tags), )); @@ -104,24 +117,22 @@ class AssignTagModelBloc if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) { - final updatedTags = List.from(currentState.tags) + final tags = List.from(currentState.tags) ..remove(event.tagToDelete); + final updatedTags = _calculateAvailableTags(allTags, tags); + emit(AssignTagModelLoaded( - tags: updatedTags, - isSaveEnabled: _validateTags(updatedTags), + tags: tags, + updatedTags: updatedTags, + isSaveEnabled: _validateTags(tags), + errorMessage: _getValidationError(tags), )); - } else { - emit(const AssignTagModelLoaded( - tags: [], - isSaveEnabled: false, - )); - } + } }); } bool _validateTags(List tags) { - final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); final isValid = uniqueTags.length == tags.length && !hasEmptyTag; @@ -129,14 +140,14 @@ class AssignTagModelBloc } String? _getValidationError(List tags) { - final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); - if (hasEmptyTag) { - return 'Tags cannot be empty.'; - } - // Check for duplicate tags - final duplicateTags = tags + + final nonEmptyTags = tags .map((tag) => tag.tag?.trim() ?? '') + .where((tag) => tag.isNotEmpty) + .toList(); + + final duplicateTags = nonEmptyTags .fold>({}, (map, tag) { map[tag] = (map[tag] ?? 0) + 1; return map; @@ -152,4 +163,16 @@ class AssignTagModelBloc return null; } + + List _calculateAvailableTags( + List allTags, List tags) { + final selectedTags = tags + .where((tag) => (tag.tag?.trim().isNotEmpty ?? false)) + .map((tag) => tag.tag!.trim()) + .toSet(); + + final availableTags = + allTags.where((tag) => !selectedTags.contains(tag.trim())).toList(); + return availableTags; + } } diff --git a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart index a51a9e8f..167a6ac2 100644 --- a/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart +++ b/lib/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart @@ -17,14 +17,17 @@ class AssignTagModelLoaded extends AssignTagModelState { final bool isSaveEnabled; final String? errorMessage; + final List updatedTags; + const AssignTagModelLoaded({ required this.tags, required this.isSaveEnabled, + required this.updatedTags, this.errorMessage, }); @override - List get props => [tags, isSaveEnabled, errorMessage]; + List get props => [tags, updatedTags, isSaveEnabled, errorMessage]; } class AssignTagModelError extends AssignTagModelState { diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 3e302d2d..d13766d4 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -16,6 +16,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/c import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; +import 'package:uuid/uuid.dart'; class AssignTagModelsDialog extends StatelessWidget { final List? products; @@ -56,7 +57,7 @@ class AssignTagModelsDialog extends StatelessWidget { ..add('Main Space'); return BlocProvider( - create: (_) => AssignTagModelBloc() + create: (_) => AssignTagModelBloc(allTags ?? []) ..add(InitializeTagModels( initialTags: initialTags, addedProducts: addedProducts, @@ -134,9 +135,6 @@ class AssignTagModelsDialog extends StatelessWidget { : List.generate(state.tags.length, (index) { final tag = state.tags[index]; final controller = controllers[index]; - final availableTags = - TagHelper.getAvailableTagModels( - allTags ?? [], state.tags, tag); return DataRow( cells: [ @@ -196,7 +194,9 @@ class AssignTagModelsDialog extends StatelessWidget { width: double .infinity, // Ensure full width for dropdown child: DialogTextfieldDropdown( - items: availableTags, + key: ValueKey( + 'dropdown_${Uuid().v4()}_${index}'), + items: state.updatedTags, initialValue: tag.tag, onSelected: (value) { controller.text = value; From 51fbe642099f737b5bc1c162c432cc452a912290 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 5 Feb 2025 16:53:36 +0300 Subject: [PATCH 174/175] fixes bugs --- lib/pages/home/bloc/home_bloc.dart | 4 +- lib/pages/home/view/home_page_web.dart | 10 +- .../add_user_dialog/bloc/users_bloc.dart | 128 +++++++++- .../add_user_dialog/view/add_user_dialog.dart | 236 +++++++++--------- .../add_user_dialog/view/basics_view.dart | 215 ++++++++-------- .../users_table/bloc/user_table_bloc.dart | 96 ++++--- .../users_table/bloc/user_table_state.dart | 5 +- .../users_table/view/users_page.dart | 44 ++-- 8 files changed, 445 insertions(+), 293 deletions(-) diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index ff35fb78..1b65adfe 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -62,7 +62,7 @@ class HomeBloc extends Bloc { emit(LoadingHome()); terms = await HomeApi().fetchTerms(); add(FetchPolicyEvent()); - emit(PolicyAgreement()); + // emit(PolicyAgreement()); } catch (e) { return; } @@ -72,7 +72,7 @@ class HomeBloc extends Bloc { try { emit(LoadingHome()); policy = await HomeApi().fetchPolicy(); - emit(PolicyAgreement()); + // emit(PolicyAgreement()); } catch (e) { return; } diff --git a/lib/pages/home/view/home_page_web.dart b/lib/pages/home/view/home_page_web.dart index 35e39821..89ff0da4 100644 --- a/lib/pages/home/view/home_page_web.dart +++ b/lib/pages/home/view/home_page_web.dart @@ -22,9 +22,9 @@ class HomeWebPage extends StatelessWidget { onPopInvoked: (didPop) => false, child: BlocConsumer( listener: (BuildContext context, state) { - if (state is HomeInitial) { + if (state is PolicyAgreement) { if (homeBloc.user!.hasAcceptedWebAgreement == false) { - Future.delayed(const Duration(seconds: 2), () { + Future.delayed(const Duration(seconds: 1), () { showDialog( context: context, barrierDismissible: false, @@ -81,7 +81,8 @@ class HomeWebPage extends StatelessWidget { width: size.width * 0.68, child: GridView.builder( itemCount: 3, //8 - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, //4 crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, @@ -93,7 +94,8 @@ class HomeWebPage extends StatelessWidget { active: homeBloc.homeItems[index].active!, name: homeBloc.homeItems[index].title!, img: homeBloc.homeItems[index].icon!, - onTap: () => homeBloc.homeItems[index].onPress(context), + onTap: () => homeBloc.homeItems[index] + .onPress(context), ); }, ), diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart index 16d6cbe6..96e49040 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart @@ -79,7 +79,7 @@ class UsersBloc extends Bloc { List updatedCommunities = []; List spacesNodes = []; - List communityIds = []; + List communityIds = []; _onLoadCommunityAndSpaces( LoadCommunityAndSpacesEvent event, Emitter emit) async { try { @@ -102,12 +102,19 @@ class UsersBloc extends Bloc { ); }).toList(), ); + originalCommunities = updatedCommunities; emit(const SpacesLoadedState()); } catch (e) { emit(ErrorState('Error loading communities and spaces: $e')); } } +// This variable holds the full original list. + List originalCommunities = []; + +// This variable holds the working list that may be filtered. + +// Build tree nodes from your data model. List _buildTreeNodes(List spaces) { return spaces.map((space) { List childNodes = @@ -123,12 +130,39 @@ class UsersBloc extends Bloc { }).toList(); } +// Optional helper method to deep clone a TreeNode. + TreeNode _cloneNode(TreeNode node) { + return TreeNode( + uuid: node.uuid, + title: node.title, + isChecked: node.isChecked, + isHighlighted: node.isHighlighted, + isExpanded: node.isExpanded, + children: node.children.map(_cloneNode).toList(), + ); + } + +// Clone an entire list of tree nodes. + List _cloneNodes(List nodes) { + return nodes.map(_cloneNode).toList(); + } + +// Your search event handler. void searchTreeNode(SearchAnode event, Emitter emit) { emit(UsersLoadingState()); + + // If the search term is empty, restore the original list. if (event.searchTerm!.isEmpty) { + // Clear any highlights on the restored copy. + updatedCommunities = _cloneNodes(originalCommunities); _clearHighlights(updatedCommunities); } else { - _searchAndHighlightNodes(updatedCommunities, event.searchTerm!); + // Start with a fresh clone of the original tree. + List freshClone = _cloneNodes(originalCommunities); + + _searchAndHighlightNodes(freshClone, event.searchTerm!); + + updatedCommunities = _filterNodes(freshClone, event.searchTerm!); } emit(ChangeStatusSteps()); } @@ -155,6 +189,91 @@ class UsersBloc extends Bloc { return anyMatch; } + List _filterNodes(List nodes, String searchTerm) { + List filteredNodes = []; + for (var node in nodes) { + bool isMatch = + node.title.toLowerCase().contains(searchTerm.toLowerCase()); + List filteredChildren = _filterNodes(node.children, searchTerm); + if (isMatch || filteredChildren.isNotEmpty) { + node.isHighlighted = isMatch; + node.children = filteredChildren; + filteredNodes.add(node); + } + } + return filteredNodes; + } + + // List _buildTreeNodes(List spaces) { + // return spaces.map((space) { + // List childNodes = + // space.children.isNotEmpty ? _buildTreeNodes(space.children) : []; + // return TreeNode( + // uuid: space.uuid!, + // title: space.name, + // isChecked: false, + // isHighlighted: false, + // isExpanded: childNodes.isNotEmpty, + // children: childNodes, + // ); + // }).toList(); + // } + + // void searchTreeNode(SearchAnode event, Emitter emit) { + // emit(UsersLoadingState()); + // if (event.searchTerm!.isEmpty) { + // _clearHighlights(updatedCommunities); + // } else { + // _searchAndHighlightNodes(updatedCommunities, event.searchTerm!); + // updatedCommunities = _filterNodes(updatedCommunities, event.searchTerm!); + // } + // emit(ChangeStatusSteps()); + // } + + // void _clearHighlights(List nodes) { + // for (var node in nodes) { + // node.isHighlighted = false; + // if (node.children.isNotEmpty) { + // _clearHighlights(node.children); + // } + // } + // } + + // bool _searchAndHighlightNodes(List nodes, String searchTerm) { + // bool anyMatch = false; + // for (var node in nodes) { + // bool isMatch = + // node.title.toLowerCase().contains(searchTerm.toLowerCase()); + // bool childMatch = _searchAndHighlightNodes(node.children, searchTerm); + // node.isHighlighted = isMatch || childMatch; + + // anyMatch = anyMatch || node.isHighlighted; + // } + // return anyMatch; + // } + + // List _filterNodes(List nodes, String searchTerm) { + // List filteredNodes = []; + // for (var node in nodes) { + // // Check if the current node's title contains the search term. + // bool isMatch = + // node.title.toLowerCase().contains(searchTerm.toLowerCase()); + + // // Recursively filter the children. + // List filteredChildren = _filterNodes(node.children, searchTerm); + + // // If the current node is a match or any of its children are, include it. + // if (isMatch || filteredChildren.isNotEmpty) { + // // Optionally, update any properties (like isHighlighted) if you still need them. + // node.isHighlighted = isMatch; + // // Replace the children with the filtered ones. + // node.children = filteredChildren; + // filteredNodes.add(node); + // } + // } + // return filteredNodes; + // } + List selectedIds = []; List getSelectedIds(List nodes) { @@ -221,7 +340,7 @@ class UsersBloc extends Bloc { lastName: lastNameController.text, phoneNumber: phoneController.text, roleUuid: roleSelected, - spaceUuids: selectedIds, + spaceUuids: selectedIds, ); if (res) { @@ -251,12 +370,11 @@ class UsersBloc extends Bloc { } } - _editInviteUser(EditInviteUsers event, Emitter emit) async { try { emit(UsersLoadingState()); List selectedIds = getSelectedIds(updatedCommunities) - .where((id) => !communityIds.contains(id)) + .where((id) => !communityIds.contains(id)) .toList(); bool res = await UserPermissionApi().editInviteUser( diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart index ec35b3fd..e4cf6107 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart @@ -26,126 +26,126 @@ class _AddNewUserDialogState extends State { create: (BuildContext context) => UsersBloc() ..add(const LoadCommunityAndSpacesEvent()) ..add(const RoleEvent()), - child: BlocConsumer( - listener: (context, state) {}, - builder: (context, state) { - final _blocRole = BlocProvider.of(context); + child: BlocConsumer(listener: (context, state) { + // print('88888==${state}'); + // if (state is SpacesLoadedState) { + // print('object'); + // _blocRole.add(const CheckRoleStepStatus()); + // } + }, builder: (context, state) { + final _blocRole = BlocProvider.of(context); - return Dialog( - child: Container( - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(20))), - width: 900, - child: Column( - children: [ - // Title - const Padding( - padding: EdgeInsets.all(8.0), - child: SizedBox( - child: Text( - "Add New User", - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w700, - color: ColorsManager.secondaryColor), + return Dialog( + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(20))), + width: 900, + child: Column( + children: [ + // Title + const Padding( + padding: EdgeInsets.all(8.0), + child: SizedBox( + child: Text( + "Add New User", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: ColorsManager.secondaryColor), + ), + ), + ), + const Divider(), + Expanded( + child: Row( + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildStep1Indicator(1, "Basics", _blocRole), + _buildStep2Indicator(2, "Spaces", _blocRole), + _buildStep3Indicator( + 3, "Role & Permissions", _blocRole), + ], + ), ), ), - ), - const Divider(), - Expanded( - child: Row( - children: [ - Expanded( - child: Container( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildStep1Indicator(1, "Basics", _blocRole), - _buildStep2Indicator(2, "Spaces", _blocRole), - _buildStep3Indicator( - 3, "Role & Permissions", _blocRole), - ], - ), - ), - ), - Container( - width: 1, - color: ColorsManager.grayBorder, - ), - Expanded( - flex: 2, - child: Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 10), - Expanded( - child: _getFormContent(), - ), - const SizedBox(height: 20), - ], - ), - ), - ), - ], + Container( + width: 1, + color: ColorsManager.grayBorder, ), - ), - const Divider(), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - InkWell( - onTap: () { - Navigator.of(context).pop(); - }, - child: const Text("Cancel"), + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + Expanded( + child: _getFormContent(), + ), + const SizedBox(height: 20), + ], ), - InkWell( - onTap: () { - _blocRole.add(const CheckEmailEvent()); - - setState(() { - if (currentStep < 3) { - currentStep++; - if (currentStep == 2) { - _blocRole.add( - const CheckStepStatus(isEditUser: false)); - } else if (currentStep == 3) { - _blocRole - .add(const CheckSpacesStepStatus()); - } - } else { - _blocRole - .add(SendInviteUsers(context: context)); - } - }); - }, - child: Text( - currentStep < 3 ? "Next" : "Save", - style: TextStyle( - color: (_blocRole.isCompleteSpaces == false || - _blocRole.isCompleteBasics == - false || - _blocRole - .isCompleteRolePermissions == - false) && - currentStep == 3 - ? ColorsManager.grayColor - : ColorsManager.secondaryColor), - ), - ), - ], + ), ), - ), - ], + ], + ), ), - )); - })); + const Divider(), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: const Text("Cancel"), + ), + InkWell( + onTap: () { + _blocRole.add(const CheckEmailEvent()); + + setState(() { + if (currentStep < 3) { + currentStep++; + if (currentStep == 2) { + _blocRole.add( + const CheckStepStatus(isEditUser: false)); + } else if (currentStep == 3) { + _blocRole.add(const CheckSpacesStepStatus()); + } + } else { + _blocRole.add(SendInviteUsers(context: context)); + } + }); + }, + child: Text( + currentStep < 3 ? "Next" : "Save", + style: TextStyle( + color: (_blocRole.isCompleteSpaces == false || + _blocRole.isCompleteBasics == false || + _blocRole.isCompleteRolePermissions == + false) && + currentStep == 3 + ? ColorsManager.grayColor + : ColorsManager.secondaryColor), + ), + ), + ], + ), + ), + ], + ), + )); + })); } Widget _getFormContent() { @@ -236,12 +236,16 @@ class _AddNewUserDialogState extends State { return GestureDetector( onTap: () { setState(() { - currentStep = step; bloc.add(const CheckStepStatus(isEditUser: false)); + currentStep = step; + Future.delayed(const Duration(milliseconds: 500), () { + bloc.add(const CheckStepStatus(isEditUser: false)); + }); if (step3 == 3) { - bloc.add(const CheckRoleStepStatus()); + Future.delayed(const Duration(seconds: 1), () { + bloc.add(const CheckRoleStepStatus()); + }); } - }); }, child: Column( diff --git a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart index 308c6c67..fa04c051 100644 --- a/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart @@ -46,117 +46,120 @@ class BasicsView extends StatelessWidget { ), Row( children: [ - SizedBox( - width: MediaQuery.of(context).size.width * 0.18, - height: MediaQuery.of(context).size.width * 0.08, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - child: Row( - children: [ - const Text( - " * ", - style: TextStyle( - color: ColorsManager.red, - fontWeight: FontWeight.w900, - fontSize: 15, + Flexible( + child: SizedBox( + // width: MediaQuery.of(context).size.width * 0.18, + height: MediaQuery.of(context).size.width * 0.08, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + const Text( + " * ", + style: TextStyle( + color: ColorsManager.red, + fontWeight: FontWeight.w900, + fontSize: 15, + ), ), - ), - Text( - 'First Name', - style: context.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w400, - fontSize: 13, - ), - ), - ], - )), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - style: - const TextStyle(color: ColorsManager.blackColor), - // onChanged: (value) { - // Future.delayed(const Duration(milliseconds: 200), - // () { - // _blocRole.add(const ValidateBasicsStep()); - // }); - // }, - controller: _blocRole.firstNameController, - decoration: inputTextFormDeco( - hintText: "Enter first name", - ).copyWith( - hintStyle: context.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w400, - fontSize: 12, - color: ColorsManager.textGray), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Enter first name'; - } - return null; - }, - ), - ), - ], - ), - ), - const SizedBox(width: 10), - SizedBox( - width: MediaQuery.of(context).size.width * 0.18, - height: MediaQuery.of(context).size.width * 0.08, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - child: Row( - children: [ - const Text( - " * ", - style: TextStyle( - color: ColorsManager.red, - fontWeight: FontWeight.w900, - fontSize: 15, - ), - ), - Text('Last Name', + Text( + 'First Name', style: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w400, fontSize: 13, - )), - ], - )), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextFormField( - // onChanged: (value) { - // Future.delayed(const Duration(milliseconds: 200), - // () { - // _blocRole.add(ValidateBasicsStep()); - // }); - // }, - controller: _blocRole.lastNameController, - style: const TextStyle(color: Colors.black), - decoration: - inputTextFormDeco(hintText: "Enter last name") - .copyWith( - hintStyle: context - .textTheme.bodyMedium - ?.copyWith( - fontWeight: FontWeight.w400, - fontSize: 12, - color: ColorsManager.textGray)), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Enter last name'; - } - return null; - }, + ), + ), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + style: const TextStyle( + color: ColorsManager.blackColor), + // onChanged: (value) { + // Future.delayed(const Duration(milliseconds: 200), + // () { + // _blocRole.add(const ValidateBasicsStep()); + // }); + // }, + controller: _blocRole.firstNameController, + decoration: inputTextFormDeco( + hintText: "Enter first name", + ).copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Enter first name'; + } + return null; + }, + ), ), - ), - ], + ], + ), + ), + ), + const SizedBox(width: 10), + Flexible( + child: SizedBox( + // width: MediaQuery.of(context).size.width * 0.18, + height: MediaQuery.of(context).size.width * 0.08, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + const Text( + " * ", + style: TextStyle( + color: ColorsManager.red, + fontWeight: FontWeight.w900, + fontSize: 15, + ), + ), + Text('Last Name', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + )), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + // onChanged: (value) { + // Future.delayed(const Duration(milliseconds: 200), + // () { + // _blocRole.add(ValidateBasicsStep()); + // }); + // }, + controller: _blocRole.lastNameController, + style: const TextStyle(color: Colors.black), + decoration: + inputTextFormDeco(hintText: "Enter last name") + .copyWith( + hintStyle: context.textTheme.bodyMedium + ?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray)), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Enter last name'; + } + return null; + }, + ), + ), + ], + ), ), ), ], diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart index 3d09e5b4..713b03f0 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart @@ -27,7 +27,16 @@ class UserTableBloc extends Bloc { int currentPage = 1; List users = []; List initialUsers = []; + + List totalUsersCount = []; String currentSortOrder = ''; + + String currentSortJopTitle = ''; + String currentSortRole = ''; + String currentSortCreatedDate = ''; + String currentSortStatus = ''; + String currentSortCreatedBy = ''; + String currentSortOrderDate = ''; List roleTypes = []; List jobTitle = []; @@ -60,6 +69,7 @@ class UserTableBloc extends Bloc { jobTitle = jobTitle.toSet().toList(); createdBy = createdBy.toSet().toList(); _handlePageChange(ChangePage(1), emit); + totalUsersCount = initialUsers; add(ChangePage(currentPage)); emit(UsersLoadedState(users: users)); } catch (e) { @@ -91,26 +101,6 @@ class UserTableBloc extends Bloc { event.userId, event.newStatus == "disabled" ? false : true); if (res == true) { add(const GetUsers()); - // users = users.map((user) { - // if (user.uuid == event.userId) { - // return RolesUserModel( - // uuid: user.uuid, - // createdAt: user.createdAt, - // email: user.email, - // firstName: user.firstName, - // lastName: user.lastName, - // roleType: user.roleType, - // status: event.newStatus, - // isEnabled: event.newStatus == "disabled" ? false : true, - // invitedBy: user.invitedBy, - // phoneNumber: user.phoneNumber, - // jobTitle: user.jobTitle, - // createdDate: user.createdDate, - // createdTime: user.createdTime, - // ); - // } - // return user; - // }).toList(); } emit(UsersLoadedState(users: users)); } catch (e) { @@ -128,7 +118,6 @@ class UserTableBloc extends Bloc { emit(UsersLoadingState()); currentSortOrder = ""; users = List.from(users); - emit(UsersLoadedState(users: users)); } else { emit(UsersLoadingState()); currentSortOrder = "Asc"; @@ -136,8 +125,12 @@ class UserTableBloc extends Bloc { .toString() .toLowerCase() .compareTo(b.firstName.toString().toLowerCase())); - emit(UsersLoadedState(users: users)); } + currentSortJopTitle = ''; + currentSortCreatedDate = ''; + currentSortStatus = ''; + currentSortCreatedBy = ''; + emit(UsersLoadedState(users: users)); } void _toggleSortUsersByNameDesc( @@ -150,13 +143,16 @@ class UserTableBloc extends Bloc { emit(UsersLoadingState()); currentSortOrder = ""; users = List.from(initialUsers); - emit(UsersLoadedState(users: users)); } else { emit(UsersLoadingState()); currentSortOrder = "Desc"; users.sort((a, b) => b.firstName!.compareTo(a.firstName!)); - emit(UsersLoadedState(users: users)); } + currentSortJopTitle = ''; + currentSortCreatedDate = ''; + currentSortStatus = ''; + currentSortCreatedBy = ''; + emit(UsersLoadedState(users: users)); } void _toggleSortUsersByDateNewestToOldest( @@ -222,6 +218,7 @@ class UserTableBloc extends Bloc { Future _searchUsers( SearchUsers event, Emitter emit) async { try { + emit(TableSearch()); final query = event.query.toLowerCase(); final filteredUsers = initialUsers.where((user) { final fullName = "${user.firstName} ${user.lastName}".toLowerCase(); @@ -250,7 +247,8 @@ class UserTableBloc extends Bloc { } void _handlePageChange(ChangePage event, Emitter emit) { - const itemsPerPage = 10; + currentPage = event.pageNumber; + const itemsPerPage = 20; final startIndex = (event.pageNumber - 1) * itemsPerPage; final endIndex = startIndex + itemsPerPage; if (startIndex >= users.length) { @@ -287,9 +285,15 @@ class UserTableBloc extends Bloc { } else if (event.sortOrder == "Desc") { currentSortOrder = "Desc"; filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); - } else { - currentSortOrder = ""; - } + } else {} + currentSortOrder = ""; + currentSortCreatedDate = ''; + currentSortStatus = ''; + currentSortCreatedBy = ''; + currentSortJopTitle = ''; + currentSortOrderDate = ""; + + totalUsersCount = filteredUsers; emit(UsersLoadedState(users: filteredUsers)); } @@ -311,9 +315,16 @@ class UserTableBloc extends Bloc { } else if (event.sortOrder == "Desc") { currentSortOrder = "Desc"; filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); - } else { - currentSortOrder = ""; - } + } else {} + currentSortOrder = ""; + currentSortCreatedDate = ''; + currentSortStatus = ''; + currentSortCreatedBy = ''; + currentSortRole = ''; + currentSortOrderDate = ""; + + totalUsersCount = filteredUsers; + emit(UsersLoadedState(users: filteredUsers)); } @@ -335,9 +346,15 @@ class UserTableBloc extends Bloc { } else if (event.sortOrder == "Desc") { currentSortOrder = "Desc"; filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); - } else { - currentSortOrder = ""; - } + } else {} + currentSortOrder = ''; + currentSortRole = ''; + currentSortCreatedDate = ''; + currentSortStatus = ''; + currentSortOrderDate = ""; + + totalUsersCount = filteredUsers; + emit(UsersLoadedState(users: filteredUsers)); } @@ -371,14 +388,17 @@ class UserTableBloc extends Bloc { } else if (event.sortOrder == "Desc") { currentSortOrder = "Desc"; filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!)); - } else { - currentSortOrder = ""; - } + totalUsersCount = filteredUsers; + } else {} + currentSortOrder = ''; + currentSortRole = ''; + currentSortCreatedDate = ''; + currentSortCreatedBy = ''; + currentSortOrderDate = ""; emit(UsersLoadedState(users: filteredUsers)); } - void _resetAllFilters(Emitter emit) { selectedRoles.clear(); selectedJobTitles.clear(); diff --git a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart index 62037315..ae99af3a 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart @@ -9,7 +9,10 @@ final class TableInitial extends UserTableState { @override List get props => []; } - +final class TableSearch extends UserTableState { + @override + List get props => []; +} final class RolesLoadingState extends UserTableState { @override List get props => []; diff --git a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart index ddfe63ff..eb2c2142 100644 --- a/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart +++ b/lib/pages/roles_and_permission/users_page/users_table/view/users_page.dart @@ -129,9 +129,12 @@ class UsersPage extends StatelessWidget { child: TextFormField( controller: searchController, onChanged: (value) { - context - .read() - .add(SearchUsers(value)); + final bloc = context.read(); + bloc.add(FilterClearEvent()); + bloc.add(SearchUsers(value)); + if (value == '') { + bloc.add(ChangePage(1)); + } }, style: const TextStyle(color: Colors.black), decoration: textBoxDecoration(radios: 15)!.copyWith( @@ -222,7 +225,7 @@ class UsersPage extends StatelessWidget { list: _blocRole.jobTitle, context: context, checkboxStates: checkboxStates, - isSelected: _blocRole.currentSortOrder, + isSelected: _blocRole.currentSortJopTitle, onOkPressed: () { searchController.clear(); _blocRole.add(FilterClearEvent()); @@ -233,14 +236,14 @@ class UsersPage extends StatelessWidget { Navigator.of(context).pop(); _blocRole.add(FilterUsersByJobEvent( selectedJob: selectedItems, - sortOrder: _blocRole.currentSortOrder, + sortOrder: _blocRole.currentSortJopTitle, )); }, onSortAtoZ: (v) { - _blocRole.currentSortOrder = v; + _blocRole.currentSortJopTitle = v; }, onSortZtoA: (v) { - _blocRole.currentSortOrder = v; + _blocRole.currentSortJopTitle = v; }, ); } @@ -263,7 +266,7 @@ class UsersPage extends StatelessWidget { list: _blocRole.roleTypes, context: context, checkboxStates: checkboxStates, - isSelected: _blocRole.currentSortOrder, + isSelected: _blocRole.currentSortRole, onOkPressed: () { searchController.clear(); _blocRole.add(FilterClearEvent()); @@ -275,13 +278,13 @@ class UsersPage extends StatelessWidget { context.read().add( FilterUsersByRoleEvent( selectedRoles: selectedItems, - sortOrder: _blocRole.currentSortOrder)); + sortOrder: _blocRole.currentSortRole)); }, onSortAtoZ: (v) { - _blocRole.currentSortOrder = v; + _blocRole.currentSortRole = v; }, onSortZtoA: (v) { - _blocRole.currentSortOrder = v; + _blocRole.currentSortRole = v; }, ); } @@ -319,7 +322,7 @@ class UsersPage extends StatelessWidget { list: _blocRole.createdBy, context: context, checkboxStates: checkboxStates, - isSelected: _blocRole.currentSortOrder, + isSelected: _blocRole.currentSortCreatedBy, onOkPressed: () { searchController.clear(); _blocRole.add(FilterClearEvent()); @@ -330,13 +333,13 @@ class UsersPage extends StatelessWidget { Navigator.of(context).pop(); _blocRole.add(FilterUsersByCreatedEvent( selectedCreatedBy: selectedItems, - sortOrder: _blocRole.currentSortOrder)); + sortOrder: _blocRole.currentSortCreatedBy)); }, onSortAtoZ: (v) { - _blocRole.currentSortOrder = v; + _blocRole.currentSortCreatedBy = v; }, onSortZtoA: (v) { - _blocRole.currentSortOrder = v; + _blocRole.currentSortCreatedBy = v; }, ); } @@ -359,7 +362,7 @@ class UsersPage extends StatelessWidget { list: _blocRole.status, context: context, checkboxStates: checkboxStates, - isSelected: _blocRole.currentSortOrder, + isSelected: _blocRole.currentSortStatus, onOkPressed: () { searchController.clear(); _blocRole.add(FilterClearEvent()); @@ -370,13 +373,13 @@ class UsersPage extends StatelessWidget { Navigator.of(context).pop(); _blocRole.add(FilterUsersByDeActevateEvent( selectedActivate: selectedItems, - sortOrder: _blocRole.currentSortOrder)); + sortOrder: _blocRole.currentSortStatus)); }, onSortAtoZ: (v) { - _blocRole.currentSortOrder = v; + _blocRole.currentSortStatus = v; }, onSortZtoA: (v) { - _blocRole.currentSortOrder = v; + _blocRole.currentSortStatus = v; }, ); } @@ -508,12 +511,11 @@ class UsersPage extends StatelessWidget { const Icon(Icons.keyboard_double_arrow_right), firstPageIcon: const Icon(Icons.keyboard_double_arrow_left), - totalPages: (_blocRole.users.length / + totalPages: (_blocRole.totalUsersCount.length / _blocRole.itemsPerPage) .ceil(), currentPage: _blocRole.currentPage, onPageChanged: (int pageNumber) { - _blocRole.currentPage = pageNumber; context .read() .add(ChangePage(pageNumber)); From 9a6bf5cbaf99842fc97af73cd506daed8378c995 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 5 Feb 2025 18:16:09 +0300 Subject: [PATCH 175/175] privacy policy fixes --- lib/pages/home/bloc/home_bloc.dart | 11 +- lib/pages/home/view/home_page_web.dart | 193 ++++++++++++++----------- 2 files changed, 116 insertions(+), 88 deletions(-) diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index 1b65adfe..fda76728 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -1,3 +1,4 @@ +import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:go_router/go_router.dart'; @@ -51,6 +52,8 @@ class HomeBloc extends Bloc { await const FlutterSecureStorage().read(key: UserModel.userUuidKey); user = await HomeApi().fetchUserInfo(uuid); add(FetchTermEvent()); + add(FetchPolicyEvent()); + emit(HomeInitial()); } catch (e) { return; @@ -61,7 +64,8 @@ class HomeBloc extends Bloc { try { emit(LoadingHome()); terms = await HomeApi().fetchTerms(); - add(FetchPolicyEvent()); + emit(HomeInitial()); + // emit(PolicyAgreement()); } catch (e) { return; @@ -72,8 +76,11 @@ class HomeBloc extends Bloc { try { emit(LoadingHome()); policy = await HomeApi().fetchPolicy(); - // emit(PolicyAgreement()); + debugPrint("Fetched policy: $policy"); + // Emit a state to trigger the UI update + emit(HomeInitial()); } catch (e) { + debugPrint("Error fetching policy: $e"); return; } } diff --git a/lib/pages/home/view/home_page_web.dart b/lib/pages/home/view/home_page_web.dart index 89ff0da4..baf18fc2 100644 --- a/lib/pages/home/view/home_page_web.dart +++ b/lib/pages/home/view/home_page_web.dart @@ -9,105 +9,126 @@ import 'package:syncrow_web/pages/home/view/home_card.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; -class HomeWebPage extends StatelessWidget { +class HomeWebPage extends StatefulWidget { const HomeWebPage({super.key}); + @override + State createState() => _HomeWebPageState(); +} + +class _HomeWebPageState extends State { + // Flag to track whether the dialog is already shown. + bool _dialogShown = false; + + @override + void initState() { + super.initState(); + final homeBloc = BlocProvider.of(context); + homeBloc.add(FetchUserInfo()); + } + @override Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; final homeBloc = BlocProvider.of(context); return PopScope( - canPop: false, - onPopInvoked: (didPop) => false, - child: BlocConsumer( - listener: (BuildContext context, state) { - if (state is PolicyAgreement) { - if (homeBloc.user!.hasAcceptedWebAgreement == false) { - Future.delayed(const Duration(seconds: 1), () { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return AgreementAndPrivacyDialog( - terms: homeBloc.terms, - policy: homeBloc.policy, - ); - }, - ).then((v) { - if (v != null) { - homeBloc.add(ConfirmUserAgreementEvent()); - homeBloc.add(const FetchUserInfo()); - } - }); + canPop: false, + onPopInvoked: (didPop) => false, + child: BlocConsumer( + listener: (BuildContext context, state) { + print('state=$state'); + if (state is HomeInitial) { + if (homeBloc.user!.hasAcceptedWebAgreement == false && + !_dialogShown) { + _dialogShown = + true; // Set the flag to true to indicate the dialog is showing. + Future.delayed(const Duration(seconds: 1), () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AgreementAndPrivacyDialog( + terms: homeBloc.terms, + policy: homeBloc.policy, + ); + }, + ).then((v) { + _dialogShown = false; + if (v != null) { + homeBloc.add(ConfirmUserAgreementEvent()); + homeBloc.add(const FetchUserInfo()); + } }); - } + }); } - }, - builder: (context, state) { - return WebScaffold( - enableMenuSidebar: false, - appBarTitle: Row( + } + }, + builder: (context, state) { + return WebScaffold( + enableMenuSidebar: false, + appBarTitle: Row( + children: [ + SvgPicture.asset( + Assets.loginLogo, + width: 150, + ), + ], + ), + scaffoldBody: SizedBox( + height: size.height, + width: size.width, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, children: [ - SvgPicture.asset( - Assets.loginLogo, - width: 150, + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: size.height * 0.1), + Text( + 'ACCESS YOUR APPS', + style: Theme.of(context) + .textTheme + .headlineLarge! + .copyWith(color: Colors.black, fontSize: 40), + ), + const SizedBox(height: 30), + Expanded( + flex: 4, + child: SizedBox( + height: size.height * 0.6, + width: size.width * 0.68, + child: GridView.builder( + itemCount: 3, // Change this count if needed. + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, // Adjust as needed. + crossAxisSpacing: 20.0, + mainAxisSpacing: 20.0, + childAspectRatio: 1.5, + ), + itemBuilder: (context, index) { + return HomeCard( + index: index, + active: homeBloc.homeItems[index].active!, + name: homeBloc.homeItems[index].title!, + img: homeBloc.homeItems[index].icon!, + onTap: () => + homeBloc.homeItems[index].onPress(context), + ); + }, + ), + ), + ), + ], ), ], ), - scaffoldBody: SizedBox( - height: size.height, - width: size.width, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox(height: size.height * 0.1), - Text( - 'ACCESS YOUR APPS', - style: Theme.of(context) - .textTheme - .headlineLarge! - .copyWith(color: Colors.black, fontSize: 40), - ), - const SizedBox(height: 30), - Expanded( - flex: 4, - child: SizedBox( - height: size.height * 0.6, - width: size.width * 0.68, - child: GridView.builder( - itemCount: 3, //8 - gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, //4 - crossAxisSpacing: 20.0, - mainAxisSpacing: 20.0, - childAspectRatio: 1.5, - ), - itemBuilder: (context, index) { - return HomeCard( - index: index, - active: homeBloc.homeItems[index].active!, - name: homeBloc.homeItems[index].title!, - img: homeBloc.homeItems[index].icon!, - onTap: () => homeBloc.homeItems[index] - .onPress(context), - ); - }, - ), - ), - ), - ], - ), - ], - ), - ), - ); - }, - )); + ), + ); + }, + ), + ); } }