From 6bc6097a7e4d11e89e66852737b67c926233ac03 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 21 Nov 2024 10:04:07 +0400 Subject: [PATCH] added edit space option --- assets/icons/door_sensor.svg | 35 +++++++ .../bloc/space_management_bloc.dart | 3 +- .../model/selected_product_model.dart | 13 +++ .../spaces_management/model/space_model.dart | 20 +--- .../view/dialogs/create_space_dialog.dart | 88 +++++++++-------- .../widgets/add_device_type_widget.dart | 24 +++-- .../widgets/community_structure_widget.dart | 51 ++++++++-- .../widgets/hoverable_button.dart | 77 +++++++++++++++ .../widgets/space_container_widget.dart | 94 ++++++++++--------- lib/services/space_mana_api.dart | 5 +- lib/utils/color_manager.dart | 1 + 11 files changed, 291 insertions(+), 120 deletions(-) create mode 100644 assets/icons/door_sensor.svg create mode 100644 lib/pages/spaces_management/model/selected_product_model.dart create mode 100644 lib/pages/spaces_management/widgets/hoverable_button.dart diff --git a/assets/icons/door_sensor.svg b/assets/icons/door_sensor.svg new file mode 100644 index 00000000..fdeb661c --- /dev/null +++ b/assets/icons/door_sensor.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/pages/spaces_management/bloc/space_management_bloc.dart b/lib/pages/spaces_management/bloc/space_management_bloc.dart index 831c6f30..41c7e234 100644 --- a/lib/pages/spaces_management/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/bloc/space_management_bloc.dart @@ -119,7 +119,6 @@ class SpaceManagementBloc extends Bloc toJson() { + return { + 'productId': productId, + 'count': count, + }; + } +} diff --git a/lib/pages/spaces_management/model/space_model.dart b/lib/pages/spaces_management/model/space_model.dart index e64b741f..9c85724d 100644 --- a/lib/pages/spaces_management/model/space_model.dart +++ b/lib/pages/spaces_management/model/space_model.dart @@ -1,14 +1,15 @@ 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'; enum SpaceStatus { newSpace, modified, unchanged } class SpaceModel { String? uuid; - final String? icon; + String? icon; final String? spaceTuyaUuid; - final String name; + String name; final bool isPrivate; final String? invitationCode; SpaceModel? parent; @@ -17,6 +18,7 @@ class SpaceModel { Offset position; bool isHovered; SpaceStatus status; + List selectedProducts; List outgoingConnections = []; // Connections from this space Connection? incomingConnection; // Connections to this space @@ -35,6 +37,7 @@ class SpaceModel { this.isHovered = false, this.incomingConnection, this.status = SpaceStatus.unchanged, + this.selectedProducts = const [], }); factory SpaceModel.fromJson(Map json) { @@ -72,19 +75,6 @@ class SpaceModel { return instance; } - @override - String toString() { - return ''' -SpaceModel { - uuid: $uuid, - name: $name, - isPrivate: $isPrivate, - position: {dx: ${position.dx}, dy: ${position.dy}}, - parentUuid: ${parent?.uuid}, - children: ${children.map((child) => child.name).toList()}, - isHovered: $isHovered -}'''; - } Map toMap() { return { diff --git a/lib/pages/spaces_management/view/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/view/dialogs/create_space_dialog.dart index d7bfeba6..76987253 100644 --- a/lib/pages/spaces_management/view/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/view/dialogs/create_space_dialog.dart @@ -3,15 +3,28 @@ 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/widgets/add_device_type_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/widgets/hoverable_button.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class CreateSpaceDialog extends StatefulWidget { - final Function(String, String) onCreateSpace; + final Function(String, String, List selectedProducts) onCreateSpace; final List? products; + final String? name; + final String? icon; + final bool isEdit; + final List selectedProducts; - const CreateSpaceDialog({super.key, required this.onCreateSpace, this.products}); + const CreateSpaceDialog( + {super.key, + required this.onCreateSpace, + this.products, + this.name, + this.icon, + this.isEdit = false, + this.selectedProducts = const []}); @override CreateSpaceDialogState createState() => CreateSpaceDialogState(); @@ -20,12 +33,21 @@ class CreateSpaceDialog extends StatefulWidget { class CreateSpaceDialogState extends State { String selectedIcon = Assets.location; String enteredName = ''; - Map selectedProducts = {}; + List selectedProducts = []; + late TextEditingController nameController; + + @override + void initState() { + super.initState(); + selectedIcon = widget.icon ?? Assets.location; + nameController = TextEditingController(text: widget.name ?? ''); + selectedProducts = widget.selectedProducts.isNotEmpty ? widget.selectedProducts : []; + } @override Widget build(BuildContext context) { return AlertDialog( - title: const Text('Create New Space'), + title: widget.isEdit ? Text('Edit Space') : Text('Create new Space'), backgroundColor: ColorsManager.whiteColors, content: SizedBox( width: 600, @@ -76,6 +98,7 @@ class CreateSpaceDialogState extends State { children: [ // Name input field TextField( + controller: nameController, onChanged: (value) { enteredName = value; }, @@ -208,8 +231,10 @@ class CreateSpaceDialogState extends State { Expanded( child: DefaultButton( onPressed: () { - if (enteredName.isNotEmpty) { - widget.onCreateSpace(enteredName, selectedIcon); // Pass the name and icon back + late String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? ''); + if (newName.isNotEmpty) { + widget.onCreateSpace( + newName, selectedIcon, selectedProducts); // Pass the name and icon back Navigator.of(context).pop(); // Close the dialog } }, @@ -270,49 +295,31 @@ class CreateSpaceDialogState extends State { Widget _buildSelectedProductsButtons(List products) { return Container( + width: 600, padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: ColorsManager.textFieldGreyColor, borderRadius: BorderRadius.circular(12), + border: Border.all( + color: ColorsManager.neutralGray, + width: 2, // Set the border width + ), ), child: Wrap( spacing: 8, // Horizontal spacing between buttons runSpacing: 8, // Vertical spacing between rows children: [ // Dynamically create a button for each selected product - for (var entry in selectedProducts.entries) - GestureDetector( + for (var product in selectedProducts) + HoverableButton( + iconPath: _mapIconToProduct(product.productId, products), + text: 'x${product.count}', onTap: () { - + setState(() { + selectedProducts.remove(product); + }); + // Handle button tap }, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - // Display the product icon - SvgPicture.asset( - _mapIconToProduct(entry.key, products), - width: 24, - height: 24, - ), - const SizedBox(width: 8), - // Display the product count - Text( - 'x${entry.value}', - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: ColorsManager.spaceColor, - ), - ), - ], - ), - ), ), // Add Button GestureDetector( @@ -331,10 +338,9 @@ class CreateSpaceDialogState extends State { ); }, child: Container( - width: 50, - height: 50, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, + color: ColorsManager.whiteColors, borderRadius: BorderRadius.circular(16), ), child: const Icon( @@ -359,7 +365,7 @@ class CreateSpaceDialogState extends State { prodId: '', prodType: '', name: '', - icon: Assets.presenceSensor, + icon: Assets.presenceSensor, ), ); diff --git a/lib/pages/spaces_management/widgets/add_device_type_widget.dart b/lib/pages/spaces_management/widgets/add_device_type_widget.dart index d6f4e8db..d069b391 100644 --- a/lib/pages/spaces_management/widgets/add_device_type_widget.dart +++ b/lib/pages/spaces_management/widgets/add_device_type_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/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/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -9,8 +10,8 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; class AddDeviceWidget extends StatefulWidget { final List? products; - final ValueChanged>? onProductsSelected; - final Map? initialSelectedProducts; + final ValueChanged>? onProductsSelected; + final List? initialSelectedProducts; const AddDeviceWidget( {super.key, this.products, this.initialSelectedProducts, this.onProductsSelected}); @@ -21,15 +22,14 @@ class AddDeviceWidget extends StatefulWidget { class _AddDeviceWidgetState extends State { late ScrollController _scrollController; - Map productCounts = {}; + List productCounts = []; @override void initState() { super.initState(); _scrollController = ScrollController(); - print(widget.initialSelectedProducts); if (widget.initialSelectedProducts != null && widget.initialSelectedProducts!.isNotEmpty) { - productCounts = widget.initialSelectedProducts!; + productCounts = List.from(widget.initialSelectedProducts!); } } @@ -112,6 +112,12 @@ class _AddDeviceWidgetState extends State { } Widget _buildDeviceTypeTile(ProductModel? deviceType) { + + SelectedProduct? existingProduct = productCounts.firstWhere( + (product) => product.productId == deviceType?.uuid, + orElse: () => SelectedProduct(productId: deviceType!.uuid, count: 0), + ); + return SizedBox( width: 75, height: 90, // Increase height if needed @@ -161,11 +167,15 @@ class _AddDeviceWidgetState extends State { const SizedBox(height: 8), // The custom counter widget aligned at the bottom CounterWidget( - initialCount: productCounts[deviceType!.uuid] ?? 0, + initialCount: existingProduct.count, onCountChanged: (newCount) { setState(() { if (newCount > 0) { - productCounts[deviceType!.uuid] = newCount; + if (!productCounts.contains(existingProduct)) { + productCounts.add(SelectedProduct(productId: deviceType!.uuid, count: newCount)); + } else { + existingProduct.count = newCount; + } } else { productCounts.remove(deviceType!.uuid); } diff --git a/lib/pages/spaces_management/widgets/community_structure_widget.dart b/lib/pages/spaces_management/widgets/community_structure_widget.dart index b3107b25..f7494a55 100644 --- a/lib/pages/spaces_management/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/widgets/community_structure_widget.dart @@ -4,6 +4,7 @@ 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/view/dialogs/create_space_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/widgets/curved_line_painter.dart'; @@ -114,6 +115,9 @@ class _CommunityStructureAreaState extends State { buildSpaceContainer: (int index) { return SpaceContainerWidget( index: index, + onDoubleTap: () { + _showEditSpaceDialog(spaces[index]); + }, icon: spaces[index].icon ?? '', name: spaces[index].name, ); @@ -239,8 +243,8 @@ class _CommunityStructureAreaState extends State { context: context, builder: (BuildContext context) { return CreateSpaceDialog( - products: widget.products, - onCreateSpace: (String name, String icon) { + products: widget.products, + onCreateSpace: (String name, String icon, List selectedProducts) { setState(() { // Set the first space in the center or use passed position Offset centerPosition = position ?? @@ -249,13 +253,13 @@ class _CommunityStructureAreaState extends State { screenSize.height / 2 - 50, // Slightly above the center vertically ); 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, + selectedProducts: selectedProducts); if (parentIndex != null && direction != null) { SpaceModel parentSpace = spaces[parentIndex]; @@ -280,6 +284,35 @@ class _CommunityStructureAreaState extends State { ); } + +void _showEditSpaceDialog(SpaceModel space) { + showDialog( + context: context, + builder: (BuildContext context) { + return CreateSpaceDialog( + products: widget.products, + name: space.name, + icon: space.icon, + 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 + } + }); + }, + // Pre-fill the dialog with current space data + key: Key(space.name), // Add a unique key to ensure dialog refresh + ); + }, + ); +} void _handleHoverChanged(int index, bool isHovered) { setState(() { spaces[index].isHovered = isHovered; diff --git a/lib/pages/spaces_management/widgets/hoverable_button.dart b/lib/pages/spaces_management/widgets/hoverable_button.dart new file mode 100644 index 00000000..bc39f87e --- /dev/null +++ b/lib/pages/spaces_management/widgets/hoverable_button.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class HoverableButton extends StatefulWidget { + final String iconPath; + final String text; + final VoidCallback onTap; + + const HoverableButton({ + Key? key, + required this.iconPath, + required this.text, + required this.onTap, + }) : super(key: key); + + @override + _HoverableButtonState createState() => _HoverableButtonState(); +} + +class _HoverableButtonState extends State { + bool isHovered = false; // Track hover state + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: widget.onTap, + child: MouseRegion( + onEnter: (_) => setState(() => isHovered = true), + onExit: (_) => setState(() => isHovered = false), + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: const EdgeInsets.symmetric(horizontal: 13, vertical: 8), + decoration: BoxDecoration( + color: isHovered ? ColorsManager.warningRed : Colors.white, // Change color on hover + borderRadius: BorderRadius.circular(16), + boxShadow: [ + if (isHovered) + BoxShadow( + color: ColorsManager.warningRed, + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (!isHovered) + SvgPicture.asset( + widget.iconPath, + width: 24, + height: 24, + ) + else + Center( + child: const Icon( + Icons.close, // Display "X" on hover + color: Colors.white, + size: 24, + )), + const SizedBox(width: 8), + if (!isHovered) ...[ + Text( + widget.text, + style: TextStyle( + fontSize: 17, + fontWeight: FontWeight.w500, + color: ColorsManager.spaceColor, + ), + ), + ], + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/widgets/space_container_widget.dart b/lib/pages/spaces_management/widgets/space_container_widget.dart index 0f98c236..bb40da51 100644 --- a/lib/pages/spaces_management/widgets/space_container_widget.dart +++ b/lib/pages/spaces_management/widgets/space_container_widget.dart @@ -2,68 +2,70 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/utils/color_manager.dart'; - class SpaceContainerWidget extends StatelessWidget { final int index; final String icon; final String name; + final VoidCallback? onDoubleTap; const SpaceContainerWidget({ super.key, required this.index, required this.icon, required this.name, + this.onDoubleTap, }); @override Widget build(BuildContext context) { - return Container( - width: 150, - height: 60, - decoration: BoxDecoration( - color: ColorsManager.whiteColors, - borderRadius: BorderRadius.circular(15), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: const Offset(0, 3), // shadow position - ), - ], - ), - child: Row( - children: [ - Container( - width: 40, - height: 60, - decoration: const BoxDecoration( - color: ColorsManager.spaceColor, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(15), - bottomLeft: Radius.circular(15), + return GestureDetector( + onDoubleTap: onDoubleTap, + child: Container( + width: 150, + height: 60, + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(15), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: const Offset(0, 3), // shadow position ), - ), - child: Center( - child: SvgPicture.asset( - icon, - color: ColorsManager.whiteColors, - width: 24, - height: 24, + ], + ), + child: Row( + children: [ + Container( + width: 40, + height: 60, + decoration: const BoxDecoration( + color: ColorsManager.spaceColor, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(15), + bottomLeft: Radius.circular(15), + ), + ), + child: Center( + child: SvgPicture.asset( + icon, + color: ColorsManager.whiteColors, + width: 24, + height: 24, + ), + ), ), - ), + const SizedBox(width: 10), + Text( + name, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ], ), - const SizedBox(width: 10), - Text( - name, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ), - ], - ), - - ); + )); } } diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 64b5c376..892d9ecd 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -1,5 +1,6 @@ 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/services/api/http_service.dart'; @@ -144,6 +145,7 @@ class CommunitySpaceManagementApi { bool isPrivate = false, required Offset position, String? icon, + required List products, }) async { try { final body = { @@ -152,7 +154,8 @@ class CommunitySpaceManagementApi { 'x': position.dx, 'y': position.dy, 'direction': direction, - 'icon': icon + '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 dca1619a..2bc3fe66 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -51,6 +51,7 @@ abstract class ColorsManager { static const Color spaceColor = Color(0xB2023DFE); static const Color counterBackgroundColor = Color(0xCCF4F4F4); static const Color neutralGray = Color(0xFFE5E5E5); + static const Color warningRed = Color(0xFFFF6465); } //background: #background: #5D5D5D;