Refactor SpaceDetails feature to replace LoadSpacesParam with LoadSpaceDetailsParam, enhancing clarity in parameter handling. Introduce ClearSpaceDetails event in SpaceDetailsBloc for better state management. Update SpaceDetailsDialog and SpaceDetailsForm to utilize new parameter and improve dialog functionality.

This commit is contained in:
Faris Armoush
2025-07-02 15:53:54 +03:00
parent 779c0fe916
commit 009b7c0316
9 changed files with 183 additions and 72 deletions

View File

@ -1,6 +1,6 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.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/models/space_details_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_spaces_param.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/domain/services/space_details_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/services/space_details_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart'; import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/services/api/http_service.dart';
@ -15,7 +15,7 @@ class RemoteSpaceDetailsService implements SpaceDetailsService {
static const _defaultErrorMessage = 'Failed to load space details'; static const _defaultErrorMessage = 'Failed to load space details';
@override @override
Future<SpaceDetailsModel> getSpaceDetails(LoadSpacesParam param) async { Future<SpaceDetailsModel> getSpaceDetails(LoadSpaceDetailsParam param) async {
try { try {
final response = await _httpService.get( final response = await _httpService.get(
path: 'endpoint', path: 'endpoint',

View File

@ -0,0 +1,7 @@
class LoadSpaceDetailsParam {
const LoadSpaceDetailsParam({
this.spaceUuid,
});
final String? spaceUuid;
}

View File

@ -1,3 +0,0 @@
class LoadSpacesParam {
const LoadSpacesParam();
}

View File

@ -1,6 +1,6 @@
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/models/space_details_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_spaces_param.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart';
abstract class SpaceDetailsService { abstract class SpaceDetailsService {
Future<SpaceDetailsModel> getSpaceDetails(LoadSpacesParam param); Future<SpaceDetailsModel> getSpaceDetails(LoadSpaceDetailsParam param);
} }

View File

@ -1,7 +1,7 @@
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.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/models/space_details_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_spaces_param.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/domain/services/space_details_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/services/space_details_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart'; import 'package:syncrow_web/services/api/api_exception.dart';
@ -11,6 +11,7 @@ part 'space_details_state.dart';
class SpaceDetailsBloc extends Bloc<SpaceDetailsEvent, SpaceDetailsState> { class SpaceDetailsBloc extends Bloc<SpaceDetailsEvent, SpaceDetailsState> {
SpaceDetailsBloc(this._spaceDetailsService) : super(SpaceDetailsInitial()) { SpaceDetailsBloc(this._spaceDetailsService) : super(SpaceDetailsInitial()) {
on<LoadSpaceDetails>(_onLoadSpaceDetails); on<LoadSpaceDetails>(_onLoadSpaceDetails);
on<ClearSpaceDetails>(_onClearSpaceDetails);
} }
final SpaceDetailsService _spaceDetailsService; final SpaceDetailsService _spaceDetailsService;
@ -31,4 +32,11 @@ class SpaceDetailsBloc extends Bloc<SpaceDetailsEvent, SpaceDetailsState> {
emit(SpaceDetailsFailure(e.toString())); emit(SpaceDetailsFailure(e.toString()));
} }
} }
void _onClearSpaceDetails(
ClearSpaceDetails event,
Emitter<SpaceDetailsState> emit,
) {
emit(SpaceDetailsInitial());
}
} }

View File

@ -7,11 +7,18 @@ sealed class SpaceDetailsEvent extends Equatable {
List<Object> get props => []; List<Object> get props => [];
} }
class LoadSpaceDetails extends SpaceDetailsEvent { final class LoadSpaceDetails extends SpaceDetailsEvent {
const LoadSpaceDetails(this.param); const LoadSpaceDetails(this.param);
final LoadSpacesParam param; final LoadSpaceDetailsParam param;
@override @override
List<Object> get props => [param]; List<Object> get props => [param];
} }
final class ClearSpaceDetails extends SpaceDetailsEvent {
const ClearSpaceDetails();
@override
List<Object> get props => [];
}

View File

@ -21,10 +21,10 @@ final class SpaceDetailsLoaded extends SpaceDetailsState {
} }
final class SpaceDetailsFailure extends SpaceDetailsState { final class SpaceDetailsFailure extends SpaceDetailsState {
final String message; final String errorMessage;
const SpaceDetailsFailure(this.message); const SpaceDetailsFailure(this.errorMessage);
@override @override
List<Object> get props => [message]; List<Object> get props => [errorMessage];
} }

View File

@ -1,17 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.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/models/space_details_model.dart';
show SpaceDetailsModel; 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/widgets/space_details_action_buttons.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_devices_box.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_form.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_icon_picker.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_name_text_field.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_box.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/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
class SpaceDetailsDialog extends StatelessWidget { class SpaceDetailsDialog extends StatefulWidget {
const SpaceDetailsDialog({ const SpaceDetailsDialog({
required this.title, required this.title,
required this.space, required this.space,
@ -23,59 +19,79 @@ class SpaceDetailsDialog extends StatelessWidget {
final SpaceDetailsModel space; final SpaceDetailsModel space;
final void Function(SpaceDetailsModel space) onSave; final void Function(SpaceDetailsModel space) onSave;
@override
State<SpaceDetailsDialog> createState() => _SpaceDetailsDialogState();
}
class _SpaceDetailsDialogState extends State<SpaceDetailsDialog> {
@override
void initState() {
final isCreateMode = widget.space.uuid.isEmpty;
if (!isCreateMode) {
context.read<SpaceDetailsBloc>().add(
LoadSpaceDetails(
LoadSpaceDetailsParam(spaceUuid: widget.space.uuid),
),
);
}
super.initState();
}
@override
void deactivate() {
context.read<SpaceDetailsBloc>().add(const ClearSpaceDetails());
super.deactivate();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( final isCreateMode = widget.space.uuid.isEmpty;
create: (context) => SpaceDetailsModelBloc(initialState: space), if (isCreateMode) {
child: Builder( return SpaceDetailsForm(
builder: (context) { title: widget.title,
final space = context.watch<SpaceDetailsModelBloc>().state; space: widget.space,
onSave: widget.onSave,
);
}
return BlocBuilder<SpaceDetailsBloc, SpaceDetailsState>(
builder: (context, state) => switch (state) {
SpaceDetailsInitial() => _buildLoadingDialog(),
SpaceDetailsLoading() => _buildLoadingDialog(),
SpaceDetailsLoaded(:final spaceDetails) => SpaceDetailsForm(
title: widget.title,
space: spaceDetails,
onSave: widget.onSave,
),
SpaceDetailsFailure(:final errorMessage) => _buildErrorDialog(
errorMessage,
),
},
);
}
Widget _buildLoadingDialog() {
return AlertDialog( return AlertDialog(
title: title, title: widget.title,
backgroundColor: ColorsManager.whiteColors, backgroundColor: ColorsManager.whiteColors,
content: SizedBox( content: const Center(child: CircularProgressIndicator()),
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,
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,
); );
}, }
),
const Spacer(), Widget _buildErrorDialog(String errorMessage) {
SpaceSubSpacesBox( return AlertDialog(
subspaces: space.subspaces, title: widget.title,
), backgroundColor: ColorsManager.whiteColors,
const SizedBox(height: 16), content: Center(
SpaceDetailsDevicesBox(space: space), child: Text(
], errorMessage,
style: context.textTheme.bodyLarge?.copyWith(
color: ColorsManager.red,
fontWeight: FontWeight.w500,
fontSize: 18,
), ),
), ),
],
),
),
actions: [
SpaceDetailsActionButtons(
onSave: () => onSave(space),
onCancel: Navigator.of(context).pop,
),
],
);
},
), ),
); );
} }

View File

@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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_action_buttons.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_devices_box.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_icon_picker.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_name_text_field.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_box.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/extension/build_context_x.dart';
class SpaceDetailsForm extends StatelessWidget {
const SpaceDetailsForm({
required this.title,
required this.space,
required this.onSave,
super.key,
});
final Widget title;
final SpaceDetailsModel space;
final void Function(SpaceDetailsModel space) onSave;
@override
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,
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,
);
},
),
const Spacer(),
SpaceSubSpacesBox(
subspaces: space.subspaces,
),
const SizedBox(height: 16),
SpaceDetailsDevicesBox(space: space),
],
),
),
],
),
),
actions: [
SpaceDetailsActionButtons(
onSave: () => onSave(space),
onCancel: Navigator.of(context).pop,
),
],
),
);
}
}