Merged with dev

This commit is contained in:
Abdullah Alassaf
2025-01-23 01:18:30 +03:00
173 changed files with 13556 additions and 564 deletions

View File

@ -99,7 +99,7 @@ class _AddDeviceWidgetState extends State<AddDeviceWidget> {
_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);
@ -114,7 +114,7 @@ class _AddDeviceWidgetState extends State<AddDeviceWidget> {
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, product: product),
);
return SizedBox(
@ -137,13 +137,14 @@ class _AddDeviceWidgetState extends State<AddDeviceWidget> {
_buildDeviceName(product, size),
const SizedBox(height: 4),
CounterWidget(
isCreate: false,
initialCount: selectedProduct.count,
onCountChanged: (newCount) {
setState(() {
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, product: product));
} else {
selectedProduct.count = newCount;
}

View File

@ -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<String> onNameSubmitted;
final List<CommunityModel> 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<CommunityStructureHeader> createState() =>
@ -137,10 +140,8 @@ class _CommunityStructureHeaderState extends State<CommunityStructureHeader> {
],
),
),
if (widget.isSave) ...[
const SizedBox(width: 8),
_buildActionButtons(theme),
],
const SizedBox(width: 8),
_buildActionButtons(theme),
],
),
],
@ -152,11 +153,19 @@ class _CommunityStructureHeaderState extends State<CommunityStructureHeader> {
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),
],
);
@ -178,7 +187,7 @@ class _CommunityStructureHeaderState extends State<CommunityStructureHeader> {
padding: 2.0,
height: buttonHeight,
elevation: 0,
borderColor: Colors.grey.shade300,
borderColor: ColorsManager.lightGrayColor,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [

View File

@ -11,12 +11,15 @@ 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';
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 +29,7 @@ class CommunityStructureArea extends StatefulWidget {
final ValueChanged<SpaceModel?>? onSpaceSelected;
final List<CommunityModel> communities;
final List<SpaceModel> spaces;
final List<SpaceTemplateModel>? spaceModels;
CommunityStructureArea({
this.selectedCommunity,
@ -34,6 +38,7 @@ class CommunityStructureArea extends StatefulWidget {
this.products,
required this.spaces,
this.onSpaceSelected,
this.spaceModels,
});
@override
@ -126,6 +131,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
isEditingName: isEditingName,
nameController: _nameController,
onSave: _saveSpaces,
selectedSpace: widget.selectedSpace,
onDelete: _onDelete,
onEditName: () {
setState(() {
@ -171,7 +177,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
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,
@ -284,12 +291,16 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
builder: (BuildContext context) {
return CreateSpaceDialog(
products: widget.products,
spaceModels: widget.spaceModels,
parentSpace: parentIndex != null ? spaces[parentIndex] : null,
onCreateSpace: (String name, String icon,
List<SelectedProduct> selectedProducts) {
onCreateSpace: (String name,
String icon,
List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel,
List<SubspaceModel>? subspaces,
List<Tag>? tags) {
setState(() {
// Set the first space in the center or use passed position
Offset centerPosition =
position ?? _getCenterPosition(screenSize);
SpaceModel newSpace = SpaceModel(
@ -299,7 +310,9 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
isPrivate: false,
children: [],
status: SpaceStatus.newSpace,
selectedProducts: selectedProducts);
spaceModel: spaceModel,
subspaces: subspaces,
tags: tags);
if (parentIndex != null && direction != null) {
SpaceModel parentSpace = spaces[parentIndex];
@ -335,14 +348,19 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
icon: space.icon,
editSpace: space,
isEdit: true,
selectedProducts: space.selectedProducts,
onCreateSpace: (String name, String icon,
List<SelectedProduct> selectedProducts) {
onCreateSpace: (String name,
String icon,
List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel,
List<SubspaceModel>? subspaces,
List<Tag>? tags) {
setState(() {
// Update the space's properties
space.name = name;
space.icon = icon;
space.selectedProducts = selectedProducts;
space.spaceModel = spaceModel;
space.subspaces = subspaces;
space.tags = tags;
if (space.status != SpaceStatus.newSpace) {
space.status = SpaceStatus.modified; // Mark as modified
@ -365,10 +383,11 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
List<SpaceModel> result = [];
void flatten(SpaceModel space) {
if (space.status == SpaceStatus.deleted) return;
if (space.status == SpaceStatus.deleted ||
space.status == SpaceStatus.parentDeleted) {
return;
}
result.add(space);
for (var child in space.children) {
flatten(child);
}
@ -436,21 +455,15 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
}
void _onDelete() {
if (widget.selectedCommunity != null &&
widget.selectedCommunity?.uuid != null &&
widget.selectedSpace == null) {
context.read<SpaceManagementBloc>().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);
}
}
_removeConnectionsForDeletedSpaces();
});
}
@ -458,7 +471,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void _markChildrenAsDeleted(SpaceModel parent) {
for (var child in parent.children) {
child.status = SpaceStatus.deleted;
child.status = SpaceStatus.parentDeleted;
_markChildrenAsDeleted(child);
}
}
@ -466,7 +480,9 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
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;
});
}

View File

@ -4,12 +4,14 @@ import 'package:syncrow_web/utils/color_manager.dart';
class CounterWidget extends StatefulWidget {
final int initialCount;
final ValueChanged<int> 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<CounterWidget> createState() => _CounterWidgetState();
@ -53,25 +55,26 @@ class _CounterWidgetState extends State<CounterWidget> {
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,
),
);

View File

@ -2,19 +2,23 @@ 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_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/widgets/add_device_type_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/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/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';
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<SelectedProduct> selectedProducts)
onCreateSpace;
final Function(String, String, List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) onCreateSpace;
final List<ProductModel>? products;
final String? name;
final String? icon;
@ -22,6 +26,9 @@ class CreateSpaceDialog extends StatefulWidget {
final List<SelectedProduct> selectedProducts;
final SpaceModel? parentSpace;
final SpaceModel? editSpace;
final List<SpaceTemplateModel>? spaceModels;
final List<SubspaceModel>? subspaces;
final List<Tag>? tags;
const CreateSpaceDialog(
{super.key,
@ -32,7 +39,10 @@ class CreateSpaceDialog extends StatefulWidget {
this.icon,
this.isEdit = false,
this.editSpace,
this.selectedProducts = const []});
this.selectedProducts = const [],
this.spaceModels,
this.subspaces,
this.tags});
@override
CreateSpaceDialogState createState() => CreateSpaceDialogState();
@ -40,12 +50,15 @@ class CreateSpaceDialog extends StatefulWidget {
class CreateSpaceDialogState extends State<CreateSpaceDialog> {
String selectedIcon = Assets.location;
SpaceTemplateModel? selectedSpaceModel;
String enteredName = '';
List<SelectedProduct> selectedProducts = [];
late TextEditingController nameController;
bool isOkButtonEnabled = false;
bool isNameFieldInvalid = false;
bool isNameFieldExist = false;
List<SubspaceModel>? subspaces;
List<Tag>? tags;
@override
void initState() {
@ -58,196 +71,485 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
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: [
const SizedBox(height: 50),
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),
if (selectedProducts.isNotEmpty)
_buildSelectedProductsButtons(widget.products ?? [])
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),
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,
),
),
],
),
),
)
: 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: [
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),
subspaces == null
? DefaultButton(
onPressed: () {
_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,
),
),
],
),
),
)
: 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 {
_showSubSpaceDialog(
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),
(tags?.isNotEmpty == true ||
subspaces?.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([
...?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,
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, 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,
),
),
],
),
),
)
],
),
),
],
),
@ -277,8 +579,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
? enteredName
: (widget.name ?? '');
if (newName.isNotEmpty) {
widget.onCreateSpace(
newName, selectedIcon, selectedProducts);
widget.onCreateSpace(newName, selectedIcon,
selectedProducts, selectedSpaceModel,subspaces,tags);
Navigator.of(context).pop();
}
}
@ -313,74 +615,6 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
);
}
Widget _buildSelectedProductsButtons(List<ProductModel> 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) ||
@ -390,20 +624,133 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
false);
}
String _mapIconToProduct(String uuid, List<ProductModel> 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,
),
void _showLinkSpaceModelDialog(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return LinkSpaceModelDialog(
spaceModels: widget.spaceModels ?? [],
onSave: (selectedModel) {
if (selectedModel != null) {
setState(() {
selectedSpaceModel = selectedModel;
subspaces = null;
});
}
},
);
},
);
}
return product.icon ?? Assets.presenceSensor;
void _showSubSpaceDialog(
BuildContext context,
String name,
final List<Tag>? spaceTags,
bool isEdit,
List<ProductModel>? products,
final List<SubspaceModel>? 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;
selectedSpaceModel = null;
});
}
});
},
);
}
void _showTagCreateDialog(
BuildContext context, String name, List<ProductModel>? 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;
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<SelectedProduct> createInitialSelectedProducts(
List<Tag>? tags, List<SubspaceModel>? subspaces) {
final Map<ProductModel, int> 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<ProductModel, int> _groupTags(List<Tag> tags) {
final Map<ProductModel, int> groupedTags = {};
for (var tag in tags) {
if (tag.product != null) {
groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1;
}
}
return groupedTags;
}
}

View File

@ -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),
);
}

View File

@ -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
),

View File

@ -1,16 +1,23 @@
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 {
class LoadedSpaceView extends StatelessWidget {
final List<CommunityModel> communities;
final CommunityModel? selectedCommunity;
final SpaceModel? selectedSpace;
final List<ProductModel>? products;
final List<SpaceTemplateModel>? spaceModels;
final bool shouldNavigateToSpaceModelPage;
const LoadedSpaceView({
super.key,
@ -18,33 +25,43 @@ class LoadedSpaceView extends StatefulWidget {
this.selectedCommunity,
this.selectedSpace,
this.products,
this.spaceModels,
required this.shouldNavigateToSpaceModelPage
});
@override
_LoadedStateViewState createState() => _LoadedStateViewState();
}
class _LoadedStateViewState extends State<LoadedSpaceView> {
@override
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.none,
children: [
Row(
children: [
SidebarWidget(
communities: widget.communities,
selectedSpaceUuid: widget.selectedSpace?.uuid ??
widget.selectedCommunity?.uuid ??
'',
),
CommunityStructureArea(
selectedCommunity: widget.selectedCommunity,
selectedSpace: widget.selectedSpace,
spaces: widget.selectedCommunity?.spaces ?? [],
products: widget.products,
communities: widget.communities,
communities: communities,
selectedSpaceUuid:
selectedSpace?.uuid ?? selectedCommunity?.uuid ?? '',
),
shouldNavigateToSpaceModelPage
? Expanded(
child: BlocProvider(
create: (context) => SpaceModelBloc(
api: SpaceModelManagementApi(),
initialSpaceModels: spaceModels ?? [],
),
child: SpaceModelPage(
products: products,
),
),
)
: CommunityStructureArea(
selectedCommunity: selectedCommunity,
selectedSpace: selectedSpace,
spaces: selectedCommunity?.spaces ?? [],
products: products,
communities: communities,
spaceModels: spaceModels,
),
],
),
const GradientCanvasBorderWidget(),

View File

@ -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),
),
),
);

View File

@ -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<ProductModel> products;
final List<SelectedProduct> selectedProducts;
final Function(List<SelectedProduct>) 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<Widget> _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<ProductModel> 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;
}
}

View File

@ -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';
@ -116,7 +118,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
children: [
Text('Communities',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Colors.black,
color: ColorsManager.blackColor,
)),
GestureDetector(
onTap: () => _navigateToBlank(context),
@ -184,6 +186,8 @@ class _SidebarWidgetState extends State<SidebarWidget> {
_selectedSpaceUuid = null; // Update the selected community
});
context.read<CenterBodyBloc>().add(CommunitySelectedEvent());
context.read<SpaceManagementBloc>().add(
SelectCommunityEvent(selectedCommunity: community),
);

View File

@ -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

View File

@ -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)),
],