Refactor Tags Service and Bloc for Improved Data Handling:

- Updated RemoteTagsService to remove LoadTagsParam and fetch project UUID internally, enhancing encapsulation and reducing parameter dependency.
- Modified TagsService interface to reflect the new loading method signature.
- Adjusted TagsBloc to align with the updated service method, simplifying the loading process.
- Enhanced AssignTagsTable and AddDeviceTypeWidget to utilize the new data flow, improving maintainability and user experience.
This commit is contained in:
Faris Armoush
2025-07-07 10:50:03 +03:00
parent e917225c3d
commit e523a83912
6 changed files with 154 additions and 135 deletions

View File

@ -1,10 +1,9 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/params/load_tags_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/services/tags_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/services/tags_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';
import 'package:syncrow_web/utils/constants/api_const.dart';
final class RemoteTagsService implements TagsService { final class RemoteTagsService implements TagsService {
const RemoteTagsService(this._httpService); const RemoteTagsService(this._httpService);
@ -14,17 +13,10 @@ final class RemoteTagsService implements TagsService {
static const _defaultErrorMessage = 'Failed to load tags'; static const _defaultErrorMessage = 'Failed to load tags';
@override @override
Future<List<Tag>> loadTags(LoadTagsParam param) async { Future<List<Tag>> loadTags() async {
if (param.projectUuid == null) {
throw Exception('Project UUID is required');
}
try { try {
final response = await _httpService.get( final response = await _httpService.get(
path: ApiEndpoints.listTags.replaceAll( path: await _makeUrl(),
'{projectUuid}',
param.projectUuid!,
),
expectedResponseModel: (json) { expectedResponseModel: (json) {
final result = json as Map<String, dynamic>; final result = json as Map<String, dynamic>;
final data = result['data'] as List<dynamic>; final data = result['data'] as List<dynamic>;
@ -46,4 +38,12 @@ final class RemoteTagsService implements TagsService {
throw APIException(formattedErrorMessage); throw APIException(formattedErrorMessage);
} }
} }
Future<String> _makeUrl() async {
final projectUuid = await ProjectManager.getProjectUUID();
if (projectUuid == null || projectUuid.isEmpty) {
throw APIException('Project UUID is required');
}
return '/projects/$projectUuid/tags';
}
} }

View File

@ -1,6 +1,5 @@
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/params/load_tags_param.dart';
abstract interface class TagsService { abstract interface class TagsService {
Future<List<Tag>> loadTags(LoadTagsParam param); Future<List<Tag>> loadTags();
} }

View File

@ -1,7 +1,6 @@
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/tags/domain/models/tag.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/params/load_tags_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/services/tags_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/services/tags_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart'; import 'package:syncrow_web/services/api/api_exception.dart';
@ -21,7 +20,7 @@ class TagsBloc extends Bloc<TagsEvent, TagsState> {
) async { ) async {
emit(TagsLoading()); emit(TagsLoading());
try { try {
final tags = await _tagsService.loadTags(event.param); final tags = await _tagsService.loadTags();
emit(TagsLoaded(tags)); emit(TagsLoaded(tags));
} on APIException catch (e) { } on APIException catch (e) {
emit(TagsFailure(e.message)); emit(TagsFailure(e.message));

View File

@ -8,10 +8,5 @@ abstract class TagsEvent extends Equatable {
} }
class LoadTags extends TagsEvent { class LoadTags extends TagsEvent {
final LoadTagsParam param; const LoadTags();
const LoadTags(this.param);
@override
List<Object?> get props => [param];
} }

View File

@ -2,6 +2,7 @@ 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/products/data/services/remote_products_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/products/data/services/remote_products_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/products/presentation/bloc/products_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/products/presentation/bloc/products_bloc.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/tags/presentation/widgets/products_grid.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/tags/presentation/widgets/products_grid.dart';
import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
@ -32,6 +33,13 @@ class AddDeviceTypeWidget extends StatelessWidget {
), ),
}, },
), ),
actions: [
SpaceDetailsActionButtons(
onSave: () {},
onCancel: Navigator.of(context).pop,
saveButtonLabel: 'Next',
),
],
), ),
), ),
); );

View File

@ -1,8 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/dialog_dropdown.dart'; import 'package:syncrow_web/common/dialog_dropdown.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/tags/domain/models/tag.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/tags/data/services/remote_tags_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/tags/presentation/bloc/tags_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/tags/presentation/widgets/product_tag_field.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/tags/presentation/widgets/product_tag_field.dart';
import 'package:syncrow_web/services/api/http_service.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';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
@ -48,10 +51,25 @@ class _AssignTagsTableState extends State<AssignTagsTable> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ClipRRect( return BlocProvider<TagsBloc>(
create: (BuildContext context) => TagsBloc(
RemoteTagsService(HTTPService()),
)..add(const LoadTags()),
child: BlocBuilder<TagsBloc, TagsState>(
builder: (context, state) {
return switch (state) {
TagsLoading() || TagsInitial() => const Center(
child: CircularProgressIndicator(),
),
TagsFailure(:final message) => Center(
child: Text(message),
),
TagsLoaded(:final tags) => ClipRRect(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
child: DataTable( child: DataTable(
headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey), headingRowColor: WidgetStateProperty.all(
ColorsManager.dataHeaderGrey,
),
key: ValueKey(widget.productAllocations.length), key: ValueKey(widget.productAllocations.length),
border: TableBorder.all( border: TableBorder.all(
color: ColorsManager.dataHeaderGrey, color: ColorsManager.dataHeaderGrey,
@ -133,20 +151,14 @@ class _AssignTagsTableState extends State<AssignTagsTable> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
width: double.infinity, width: double.infinity,
child: ProductTagField( child: ProductTagField(
key: ValueKey('dropdown_${const Uuid().v4()}_$index'), key: ValueKey(
'dropdown_${const Uuid().v4()}_$index'),
productName: productAllocation.product.uuid, productName: productAllocation.product.uuid,
initialValue: null, initialValue: null,
onSelected: (value) { onSelected: (value) {
controller.text = value.name; controller.text = value.name;
}, },
items: const [ items: tags,
Tag(
uuid: '',
name: 'Tag',
createdAt: '',
updatedAt: '',
),
],
), ),
), ),
), ),
@ -156,7 +168,8 @@ class _AssignTagsTableState extends State<AssignTagsTable> {
child: DialogDropdown( child: DialogDropdown(
items: const [], items: const [],
// items: widget.locations, // items: widget.locations,
selectedValue: productAllocation.tag.name.isEmpty selectedValue:
productAllocation.tag.name.isEmpty
? 'Main Space' ? 'Main Space'
: productAllocation.tag.name, : productAllocation.tag.name,
onSelected: (value) {}, onSelected: (value) {},
@ -166,6 +179,11 @@ class _AssignTagsTableState extends State<AssignTagsTable> {
); );
}), }),
), ),
),
_ => const SizedBox.shrink(),
};
},
),
); );
} }
} }