added edit space option

This commit is contained in:
hannathkadher
2024-11-21 10:04:07 +04:00
parent 6fd845a9fc
commit 6bc6097a7e
11 changed files with 291 additions and 120 deletions
@@ -119,7 +119,6 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
try {
final updatedSpaces = await saveSpacesHierarchically(event.spaces, event.communityUuid);
emit(SpaceCreationSuccess(spaces: updatedSpaces));
add(LoadCommunityAndSpacesEvent());
} catch (e) {
@@ -158,6 +157,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
position: space.position,
icon: space.icon,
direction: space.incomingConnection?.direction,
products: space.selectedProducts
);
space.uuid = response?.uuid;
}
@@ -0,0 +1,13 @@
class SelectedProduct {
final String productId;
int count;
SelectedProduct({required this.productId, required this.count});
Map<String, dynamic> toJson() {
return {
'productId': productId,
'count': count,
};
}
}
@@ -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<SelectedProduct> selectedProducts;
List<Connection> 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<String, dynamic> 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<String, dynamic> toMap() {
return {
@@ -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<SelectedProduct> selectedProducts) onCreateSpace;
final List<ProductModel>? products;
final String? name;
final String? icon;
final bool isEdit;
final List<SelectedProduct> 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<CreateSpaceDialog> {
String selectedIcon = Assets.location;
String enteredName = '';
Map<String, int> selectedProducts = {};
List<SelectedProduct> 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<CreateSpaceDialog> {
children: [
// Name input field
TextField(
controller: nameController,
onChanged: (value) {
enteredName = value;
},
@@ -208,8 +231,10 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
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<CreateSpaceDialog> {
Widget _buildSelectedProductsButtons(List<ProductModel> 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<CreateSpaceDialog> {
);
},
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<CreateSpaceDialog> {
prodId: '',
prodType: '',
name: '',
icon: Assets.presenceSensor,
icon: Assets.presenceSensor,
),
);
@@ -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<ProductModel>? products;
final ValueChanged<Map<String, int>>? onProductsSelected;
final Map<String, int>? initialSelectedProducts;
final ValueChanged<List<SelectedProduct>>? onProductsSelected;
final List<SelectedProduct>? initialSelectedProducts;
const AddDeviceWidget(
{super.key, this.products, this.initialSelectedProducts, this.onProductsSelected});
@@ -21,15 +22,14 @@ class AddDeviceWidget extends StatefulWidget {
class _AddDeviceWidgetState extends State<AddDeviceWidget> {
late ScrollController _scrollController;
Map<String, int> productCounts = {};
List<SelectedProduct> 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<AddDeviceWidget> {
}
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<AddDeviceWidget> {
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);
}
@@ -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<CommunityStructureArea> {
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<CommunityStructureArea> {
context: context,
builder: (BuildContext context) {
return CreateSpaceDialog(
products: widget.products,
onCreateSpace: (String name, String icon) {
products: widget.products,
onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts) {
setState(() {
// Set the first space in the center or use passed position
Offset centerPosition = position ??
@@ -249,13 +253,13 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
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<CommunityStructureArea> {
);
}
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<SelectedProduct> 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;
@@ -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<HoverableButton> {
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,
),
),
],
],
),
),
),
);
}
}
@@ -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,
),
),
],
),
);
));
}
}
+4 -1
View File
@@ -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<SelectedProduct> 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;
+1
View File
@@ -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;