Add factory method empty to SpaceModel for default instance creation. Refactor SpaceDetailsDialog and related widgets to utilize SpaceModel, enhancing parameter handling and state management in space creation and editing flows.

This commit is contained in:
Faris Armoush
2025-07-02 17:05:56 +03:00
parent 71cf4b9feb
commit c221c8499f
7 changed files with 130 additions and 91 deletions

View File

@ -19,6 +19,16 @@ class SpaceModel extends Equatable {
required this.parent,
});
factory SpaceModel.empty() => const SpaceModel(
uuid: '',
createdAt: null,
updatedAt: null,
spaceName: '',
icon: '',
children: [],
parent: null,
);
factory SpaceModel.fromJson(Map<String, dynamic> json) {
return SpaceModel(
uuid: json['uuid'] as String? ?? '',

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart';
abstract final class SpaceDetailsDialogHelper {
@ -9,8 +8,8 @@ abstract final class SpaceDetailsDialogHelper {
context: context,
builder: (_) => SpaceDetailsDialog(
title: const Text('Create Space'),
space: SpaceDetailsModel.empty(),
onSave: (space) {},
spaceModel: SpaceModel.empty(),
onSave: (space) => print(space),
),
);
}
@ -23,13 +22,7 @@ abstract final class SpaceDetailsDialogHelper {
context: context,
builder: (_) => SpaceDetailsDialog(
title: const Text('Edit Space'),
space: SpaceDetailsModel(
uuid: spaceModel.uuid,
spaceName: spaceModel.spaceName,
icon: spaceModel.icon,
productAllocations: const [],
subspaces: const [],
),
spaceModel: spaceModel,
onSave: (space) {},
),
);

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
@ -10,13 +11,13 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart';
class SpaceDetailsDialog extends StatefulWidget {
const SpaceDetailsDialog({
required this.title,
required this.space,
required this.spaceModel,
required this.onSave,
super.key,
});
final Widget title;
final SpaceDetailsModel space;
final SpaceModel spaceModel;
final void Function(SpaceDetailsModel space) onSave;
@override
@ -26,10 +27,10 @@ class SpaceDetailsDialog extends StatefulWidget {
class _SpaceDetailsDialogState extends State<SpaceDetailsDialog> {
@override
void initState() {
final isCreateMode = widget.space.uuid.isEmpty;
final isCreateMode = widget.spaceModel.uuid.isEmpty;
if (!isCreateMode) {
final param = LoadSpaceDetailsParam(spaceUuid: widget.space.uuid);
final param = LoadSpaceDetailsParam(spaceUuid: widget.spaceModel.uuid);
context.read<SpaceDetailsBloc>().add(LoadSpaceDetails(param));
}
super.initState();
@ -37,11 +38,11 @@ class _SpaceDetailsDialogState extends State<SpaceDetailsDialog> {
@override
Widget build(BuildContext context) {
final isCreateMode = widget.space.uuid.isEmpty;
final isCreateMode = widget.spaceModel.uuid.isEmpty;
if (isCreateMode) {
return SpaceDetailsForm(
title: widget.title,
space: widget.space,
space: SpaceDetailsModel.empty(),
onSave: widget.onSave,
);
}

View File

@ -26,51 +26,51 @@ class SpaceDetailsForm extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SpaceDetailsModelBloc(initialState: space),
child: AlertDialog(
title: title,
backgroundColor: ColorsManager.whiteColors,
content: SizedBox(
height: context.screenHeight * 0.25,
child: Row(
spacing: 20,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: SpaceIconPicker(iconPath: space.icon)),
Expanded(
flex: 2,
child: Column(
mainAxisSize: MainAxisSize.min,
child: BlocBuilder<SpaceDetailsModelBloc, SpaceDetailsModel>(
buildWhen: (previous, current) => previous != current,
builder: (context, state) {
return AlertDialog(
title: title,
backgroundColor: ColorsManager.whiteColors,
content: SizedBox(
height: context.screenHeight * 0.3,
child: Row(
spacing: 20,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SpaceNameTextField(
initialValue: space.spaceName,
isNameFieldExist: (value) {
final subspaces = space.subspaces;
if (subspaces.isEmpty) return false;
return subspaces.any(
(subspace) => subspace.name == value,
);
},
Expanded(child: SpaceIconPicker(iconPath: state.icon)),
Expanded(
flex: 2,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SpaceNameTextField(
initialValue: state.spaceName,
isNameFieldExist: (value) => state.subspaces.any(
(subspace) => subspace.name == value,
),
),
const Spacer(),
SpaceSubSpacesBox(
subspaces: state.subspaces,
),
const SizedBox(height: 16),
SpaceDetailsDevicesBox(space: state),
],
),
),
const Spacer(),
SpaceSubSpacesBox(
subspaces: space.subspaces,
),
const SizedBox(height: 16),
SpaceDetailsDevicesBox(space: space),
],
),
),
],
),
),
actions: [
SpaceDetailsActionButtons(
onSave: () => onSave(space),
onCancel: Navigator.of(context).pop,
),
],
),
actions: [
SpaceDetailsActionButtons(
onSave: () => onSave(state),
onCancel: Navigator.of(context).pop,
),
],
);
}),
);
}
}

View File

@ -1,9 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/edit_chip.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/button_content_widget.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_dialog.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_name_display_widget.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/update_space/presentation/bloc/space_details_model_bloc/space_details_model_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -23,10 +25,7 @@ class SpaceSubSpacesBox extends StatelessWidget {
padding: EdgeInsets.zero,
overlayColor: ColorsManager.transparentColor,
),
onPressed: () => showDialog<void>(
context: context,
builder: (_) => SpaceSubSpacesDialog(subspaces: subspaces),
),
onPressed: () => _showSubSpacesDialog(context),
child: const ButtonContentWidget(
svgAssets: Assets.addIcon,
label: 'Create Sub Spaces',
@ -54,10 +53,7 @@ class SpaceSubSpacesBox extends StatelessWidget {
(e) => SubspaceNameDisplayWidget(subSpace: e),
),
EditChip(
onTap: () => showDialog<void>(
context: context,
builder: (_) => const SpaceSubSpacesDialog(subspaces: []),
),
onTap: () => _showSubSpacesDialog(context),
),
],
),
@ -66,4 +62,19 @@ class SpaceSubSpacesBox extends StatelessWidget {
],
);
}
void _showSubSpacesDialog(BuildContext context) {
showDialog<void>(
context: context,
barrierDismissible: false,
builder: (_) => SpaceSubSpacesDialog(
subspaces: subspaces,
onSave: (subspaces) {
context.read<SpaceDetailsModelBloc>().add(
UpdateSpaceDetailsSubspaces(subspaces),
);
},
),
);
}
}

View File

@ -7,10 +7,12 @@ import 'package:uuid/uuid.dart';
class SpaceSubSpacesDialog extends StatefulWidget {
const SpaceSubSpacesDialog({
required this.subspaces,
required this.onSave,
super.key,
});
final List<Subspace> subspaces;
final void Function(List<Subspace> subspaces) onSave;
@override
State<SpaceSubSpacesDialog> createState() => _SpaceSubSpacesDialogState();
@ -46,7 +48,10 @@ class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
() => _subspaces = _subspaces.where((s) => s.uuid != uuid).toList(),
);
void _handleSave() => Navigator.of(context).pop(_subspaces);
void _handleSave() {
widget.onSave(_subspaces);
Navigator.of(context).pop();
}
@override
Widget build(BuildContext context) {

View File

@ -17,16 +17,19 @@ class SubspaceNameDisplayWidget extends StatefulWidget {
class _SubspaceNameDisplayWidgetState extends State<SubspaceNameDisplayWidget> {
late final TextEditingController _controller;
late final FocusNode _focusNode;
bool isEditing = false;
@override
void initState() {
_controller = TextEditingController(text: widget.subSpace.name);
_focusNode = FocusNode();
super.initState();
}
@override
void dispose() {
_controller.dispose();
_focusNode.dispose();
super.dispose();
}
@ -36,38 +39,54 @@ class _SubspaceNameDisplayWidgetState extends State<SubspaceNameDisplayWidget> {
color: ColorsManager.spaceColor,
);
return InkWell(
onTap: () => setState(() => isEditing = true),
child: Visibility(
visible: isEditing,
replacement: Text(
widget.subSpace.name,
style: textStyle,
onTap: () {
setState(() => isEditing = true);
_focusNode.requestFocus();
},
child: Chip(
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: TextField(
controller: _controller,
style: textStyle,
decoration: const InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsetsDirectional.symmetric(
horizontal: 8,
label: Visibility(
visible: isEditing,
replacement: Text(
widget.subSpace.name,
style: textStyle,
),
child: SizedBox(
width: context.screenWidth * 0.065,
height: context.screenHeight * 0.025,
child: TextField(
focusNode: _focusNode,
controller: _controller,
style: textStyle,
decoration: const InputDecoration.collapsed(hintText: ''),
onTapOutside: (_) => _onFinishEditing(),
onSubmitted: (value) {
final bloc = context.read<SpaceDetailsModelBloc>();
bloc.add(
UpdateSpaceDetailsSubspaces(
bloc.state.subspaces
.map(
(e) => e.uuid == widget.subSpace.uuid
? e.copyWith(name: value)
: e,
)
.toList(),
),
);
_onFinishEditing();
},
),
),
onSubmitted: (value) {
final bloc = context.read<SpaceDetailsModelBloc>();
bloc.add(
UpdateSpaceDetailsSubspaces(
bloc.state.subspaces
.map(
(e) => e.uuid == widget.subSpace.uuid
? e.copyWith(name: value)
: e,
)
.toList(),
),
);
},
),
),
);
}
void _onFinishEditing() {
setState(() => isEditing = false);
_focusNode.unfocus();
}
}