subspace model

This commit is contained in:
hannathkadher
2025-01-05 11:45:05 +04:00
parent 944b981ee0
commit 90e5499f92
16 changed files with 346 additions and 148 deletions

View File

@ -1,2 +1,2 @@
ENV_NAME=development ENV_NAME=development
BASE_URL=https://syncrow-dev.azurewebsites.net BASE_URL=http://localhost:4001

3
.gitignore vendored
View File

@ -41,3 +41,6 @@ app.*.map.json
/android/app/debug /android/app/debug
/android/app/profile /android/app/profile
/android/app/release /android/app/release
lib/utils/constants/temp_const.dart
.env.development
lib/utils/constants/temp_const.dart

View File

@ -330,6 +330,7 @@ class SpaceManagementBloc
) { ) {
final communities = List<CommunityModel>.from(previousState.communities); final communities = List<CommunityModel>.from(previousState.communities);
for (var community in communities) { for (var community in communities) {
if (community.uuid == communityUuid) { if (community.uuid == communityUuid) {
community.spaces = allSpaces; community.spaces = allSpaces;

View File

@ -48,7 +48,8 @@ class _LoadedStateViewState extends State<LoadedSpaceView> {
hasSpaceModels hasSpaceModels
? Expanded( ? Expanded(
child: SpaceModelPage( child: SpaceModelPage(
spaceModels: widget.spaceModels ??[], spaceModels: widget.spaceModels ?? [],
products: widget.products,
)) ))
: CommunityStructureArea( : CommunityStructureArea(
selectedCommunity: widget.selectedCommunity, selectedCommunity: widget.selectedCommunity,

View File

@ -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<CreateSpaceModelEvent, CreateSpaceModelState> {
SpaceTemplateModel? _space;
CreateSpaceModelBloc() : super(CreateSpaceModelInitial()) {
on<LoadSpaceTemplate>((event, emit) {
emit(CreateSpaceModelLoading());
Future.delayed(const Duration(seconds: 1), () {
if (_space != null) {
emit(CreateSpaceModelLoaded(_space!));
} else {
emit(CreateSpaceModelError("No space template found"));
}
});
});
on<UpdateSpaceTemplate>((event, emit) {
_space = event.spaceTemplate;
emit(CreateSpaceModelLoaded(_space!));
});
}
}

View File

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

View File

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

View File

@ -1,38 +1,34 @@
import 'package:flutter_bloc/flutter_bloc.dart'; 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/space_template_model.dart';
import 'package:syncrow_web/utils/constants/action_enum.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<SubspaceTemplateModel> subSpaces;
SubSpaceModelState(this.subSpaces);
}
// BLoC // BLoC
class SubSpaceModelBloc extends Bloc<SubSpaceModelEvent, SubSpaceModelState> { class SubSpaceModelBloc extends Bloc<SubSpaceModelEvent, SubSpaceModelState> {
SubSpaceModelBloc() : super(SubSpaceModelState([])) { SubSpaceModelBloc() : super(SubSpaceModelState([], [])) {
on<AddSubSpaceModel>((event, emit) { on<AddSubSpaceModel>((event, emit) {
final updatedSubSpaces = List<SubspaceTemplateModel>.from(state.subSpaces) final updatedSubSpaces = List<SubspaceTemplateModel>.from(state.subSpaces)
..add(event.subSpace); ..add(event.subSpace);
emit(SubSpaceModelState(updatedSubSpaces)); emit(SubSpaceModelState(updatedSubSpaces, []));
}); });
on<RemoveSubSpaceModel>((event, emit) { on<RemoveSubSpaceModel>((event, emit) {
final updatedSubSpaces = List<SubspaceTemplateModel>.from(state.subSpaces) final updatedSubSpaces = List<SubspaceTemplateModel>.from(state.subSpaces)
..remove(event.subSpace); ..remove(event.subSpace);
emit(SubSpaceModelState(updatedSubSpaces));
final updatedSubspaceModels = List<UpdateSubspaceTemplateModel>.from(
state.updatedSubSpaceModels,
);
if (event.subSpace.uuid?.isNotEmpty ?? false) {
updatedSubspaceModels.add(UpdateSubspaceTemplateModel(
action: Action.delete,
uuid: event.subSpace.uuid!,
));
}
emit(SubSpaceModelState(updatedSubSpaces, updatedSubspaceModels));
}); });
} }
} }

View File

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

View File

@ -0,0 +1,11 @@
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
class SubSpaceModelState {
final List<SubspaceTemplateModel> subSpaces;
final List<UpdateSubspaceTemplateModel> updatedSubSpaceModels;
SubSpaceModelState(
this.subSpaces,
this.updatedSubSpaceModels,
);
}

View File

@ -1,25 +1,20 @@
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; 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 'package:uuid/uuid.dart';
class SpaceTemplateModel { class SpaceTemplateModel {
final String uuid; final String? uuid;
final DateTime createdAt;
final DateTime updatedAt;
final String modelName; final String modelName;
final bool disabled; final List<SubspaceTemplateModel>? subspaceModels;
final List<SubspaceTemplateModel> subspaceModels; final List<TagModel>? tags;
final List<TagModel> tags;
String internalId; String internalId;
SpaceTemplateModel({ SpaceTemplateModel({
required this.uuid, this.uuid,
String? internalId, String? internalId,
required this.createdAt,
required this.updatedAt,
required this.modelName, required this.modelName,
required this.disabled, this.subspaceModels,
required this.subspaceModels, this.tags,
required this.tags,
}) : internalId = internalId ?? const Uuid().v4(); }) : internalId = internalId ?? const Uuid().v4();
factory SpaceTemplateModel.fromJson(Map<String, dynamic> json) { factory SpaceTemplateModel.fromJson(Map<String, dynamic> json) {
@ -28,10 +23,7 @@ class SpaceTemplateModel {
return SpaceTemplateModel( return SpaceTemplateModel(
uuid: json['uuid'] ?? '', uuid: json['uuid'] ?? '',
internalId: internalId, internalId: internalId,
createdAt: DateTime.parse(json['createdAt']),
updatedAt: DateTime.parse(json['updatedAt']),
modelName: json['modelName'] ?? '', modelName: json['modelName'] ?? '',
disabled: json['disabled'] ?? false,
subspaceModels: (json['subspaceModels'] as List) subspaceModels: (json['subspaceModels'] as List)
.map((item) => SubspaceTemplateModel.fromJson(item)) .map((item) => SubspaceTemplateModel.fromJson(item))
.toList(), .toList(),
@ -44,12 +36,9 @@ class SpaceTemplateModel {
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'uuid': uuid, 'uuid': uuid,
'createdAt': createdAt.toIso8601String(),
'updatedAt': updatedAt.toIso8601String(),
'modelName': modelName, 'modelName': modelName,
'disabled': disabled, 'subspaceModels': subspaceModels?.map((e) => e.toJson()).toList(),
'subspaceModels': subspaceModels.map((e) => e.toJson()).toList(), 'tags': tags?.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<UpdateTagModel>? tags;
UpdateSubspaceTemplateModel({
required this.action,
required this.uuid,
this.subspaceName,
this.tags,
});
factory UpdateSubspaceTemplateModel.fromJson(Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> toJson() {
return {
'action': action.value,
'uuid': uuid,
'tag': tag,
'disabled': disabled,
'product': product?.toMap(),
};
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; 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/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/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/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 { class SpaceModelPage extends StatelessWidget {
final List<SpaceTemplateModel> spaceModels; final List<SpaceTemplateModel> spaceModels;
final List<ProductModel>? products;
const SpaceModelPage({Key? key, required this.spaceModels}) : super(key: key); const SpaceModelPage({Key? key, required this.spaceModels, this.products})
: super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -33,7 +36,7 @@ class SpaceModelPage extends StatelessWidget {
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return const CreateSpaceModelDialog(); return CreateSpaceModelDialog(products: products);
}, },
); );
}, },

View File

@ -1,12 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_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/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/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class CreateSpaceModelDialog extends StatelessWidget { class CreateSpaceModelDialog extends StatelessWidget {
const CreateSpaceModelDialog({Key? key}) : super(key: key); final List<ProductModel>? products;
const CreateSpaceModelDialog({Key? key, this.products}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -50,94 +53,54 @@ class CreateSpaceModelDialog extends StatelessWidget {
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
GestureDetector( TextButton(
onTap: () async { onPressed: () async {
final result = await showDialog( final result = await showDialog<String>(
barrierDismissible: false,
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return CreateSubSpaceModelDialog( return const CreateSubSpaceModelDialog(
isEdit: true, isEdit: true,
dialogTitle: 'Create Sub-space', 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( style: TextButton.styleFrom(
width: screenWidth * 0.25, padding: EdgeInsets.zero,
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,
),
),
),
],
),
),
), ),
child: _buildButtonContent(
context,
icon: Icons.add,
label: 'Create Sub Space',
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
SizedBox( TextButton(
width: screenWidth * 0.25, onPressed: () async {
child: Container( final result = await showDialog<bool>(
decoration: BoxDecoration( barrierDismissible: false,
color: ColorsManager.textFieldGreyColor, context: context,
border: builder: (context) => AddDeviceWidget(
Border.all(color: ColorsManager.neutralGray, width: 3.0), products: products,
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,
),
),
),
],
), ),
);
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), const SizedBox(height: 20),
@ -149,11 +112,14 @@ class CreateSpaceModelDialog extends StatelessWidget {
child: CancelButton( child: CancelButton(
label: 'Cancel', label: 'Cancel',
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
)), ),
),
const SizedBox(width: 10), const SizedBox(width: 10),
Expanded( Expanded(
child: DefaultButton( child: DefaultButton(
onPressed: () {}, onPressed: () {
Navigator.of(context).pop();
},
backgroundColor: ColorsManager.secondaryColor, backgroundColor: ColorsManager.secondaryColor,
borderRadius: 10, borderRadius: 10,
foregroundColor: ColorsManager.whiteColors, 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,
),
),
),
],
),
),
),
);
}
} }

View File

@ -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/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_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_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/space_template_model.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class CreateSubSpaceModelDialog extends StatefulWidget { class CreateSubSpaceModelDialog extends StatefulWidget {
final bool isEdit; // Flag to determine if it's edit or create final bool isEdit;
final String dialogTitle; // Title for the dialog final String dialogTitle;
final List<SubspaceTemplateModel>? existingSubSpaces; // For edit mode final List<SubspaceTemplateModel>? existingSubSpaces;
const CreateSubSpaceModelDialog({ const CreateSubSpaceModelDialog({
super.key, super.key,
@ -52,8 +54,17 @@ class _CreateSubSpaceModelDialogState extends State<CreateSubSpaceModelDialog> {
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
child: BlocProvider( 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<SubSpaceModelBloc, SubSpaceModelState>( child: BlocBuilder<SubSpaceModelBloc, SubSpaceModelState>(
builder: (context, state) { builder: (context, state) {
return SizedBox( return SizedBox(

View File

@ -13,12 +13,15 @@ class SpaceModelCardWidget extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Map<String, int> productTagCount = {}; final Map<String, int> productTagCount = {};
for (var tag in model.tags) { if (model.tags != null) {
for (var tag in model.tags!) {
final prodIcon = tag.product?.icon ?? 'Unknown'; final prodIcon = tag.product?.icon ?? 'Unknown';
productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1;
} }
}
for (var subspace in model.subspaceModels) { if (model.subspaceModels != null) {
for (var subspace in model.subspaceModels!) {
if (subspace.tags != null) { if (subspace.tags != null) {
for (var tag in subspace.tags!) { for (var tag in subspace.tags!) {
final prodIcon = tag.product?.icon ?? 'Unknown'; final prodIcon = tag.product?.icon ?? 'Unknown';
@ -26,6 +29,7 @@ class SpaceModelCardWidget extends StatelessWidget {
} }
} }
} }
}
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -65,7 +69,7 @@ class SpaceModelCardWidget extends StatelessWidget {
spacing: 3.0, spacing: 3.0,
runSpacing: 3.0, runSpacing: 3.0,
children: [ children: [
for (var subspace in model.subspaceModels.take(3)) for (var subspace in model.subspaceModels!.take(3))
SubspaceChipWidget(subspace: subspace.subspaceName), SubspaceChipWidget(subspace: subspace.subspaceName),
], ],
), ),

View File

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