feat: Refactor code structure and optimize performance across multiple modules

This commit is contained in:
Achintha Isuru
2025-11-17 23:29:28 -05:00
parent 831570f2e0
commit a64cbd9edf
1508 changed files with 105319 additions and 0 deletions

View File

@@ -0,0 +1,230 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow/core/application/di/injectable.dart';
import 'package:krow/core/data/models/event/addon_model.dart';
import 'package:krow/core/data/models/event/business_member_model.dart';
import 'package:krow/core/data/models/event/event_model.dart';
import 'package:krow/core/data/models/event/hub_model.dart';
import 'package:krow/core/data/models/event/tag_model.dart';
import 'package:krow/core/data/models/shift/business_skill_model.dart';
import 'package:krow/core/data/models/shift/department_model.dart';
import 'package:krow/core/entity/event_entity.dart';
import 'package:krow/core/entity/shift_entity.dart';
import 'package:krow/core/sevices/create_event_service/create_event_service.dart';
import 'package:krow/features/create_event/domain/create_event_repository.dart';
import 'package:krow/features/create_event/domain/input_validator.dart';
import 'package:krow/features/create_event/presentation/create_shift_details_section/bloc/create_shift_details_bloc.dart';
part 'create_event_event.dart';
part 'create_event_state.dart';
class CreateEventBloc extends Bloc<CreateEventEvent, CreateEventState> {
CreateEventBloc() : super(CreateEventState(entity: EventEntity.empty())) {
on<CreateEventInit>(_onInit);
// on<CreateEventChangeContractType>(_onChangeContractType);
on<CreateEventChangeHub>(_onChangeHub);
// on<CreateEventChangeContractNumber>(_onChangeContractNumber);
on<CreateEventChangePoNumber>(_onChangePoNumber);
on<CreateEventTagSelected>(_onTagSelected);
on<CreateEventAddShift>(_onAddShift);
on<CreateEventRemoveShift>(_onRemoveShift);
on<CreateEventAddInfoChange>(_onAddInfoChange);
on<CreateEventNameChange>(_onNameChange);
on<CreateEventToggleAddon>(_onToggleAddon);
on<CreateEventEntityUpdatedEvent>(_onEntityUpdated);
on<CreateEventValidateAndPreview>(_onValidateAndPreview);
on<DeleteDraftEvent>(_onDeleteDraft);
}
Future<void> _onInit(
CreateEventInit event, Emitter<CreateEventState> emit) async {
emit(state.copyWith(inLoading: true));
late EventEntity entity;
bool? isEdit = event.eventModel != null;
if (isEdit) {
entity = EventEntity.fromEventDto(event.eventModel!);
} else {
entity = EventEntity.empty();
}
List<ShiftViewModel> shiftViewModels = [
...entity.shifts?.map((shiftEntity) {
return ShiftViewModel(
id: shiftEntity.id,
bloc: CreateShiftDetailsBloc(expanded: !isEdit)
..add(CreateShiftInitializeEvent(shiftEntity)),
);
}).toList() ??
[],
];
emit(state.copyWith(
recurringType: event.recurringType,
entity: entity,
tags: [
//placeholder for tags - prevent UI jumping
TagModel(id: '1', name: ' '),
TagModel(id: '2', name: ' '),
TagModel(id: '3', name: ' ')
],
shifts: shiftViewModels));
List<Object> results;
try {
results = await Future.wait([
getIt<CreateEventRepository>().getHubs().onError((e, s) {
return [];
}),
getIt<CreateEventRepository>().getAddons().onError((e, s) => []),
getIt<CreateEventRepository>().getTags().onError((e, s) => []),
getIt<CreateEventRepository>().getContacts().onError((e, s) => []),
getIt<CreateEventRepository>().getSkills().onError((e, s) => []),
]);
} catch (e) {
emit(state.copyWith(inLoading: false));
return;
}
var hubs = results[0] as List<HubModel>;
var addons = results[1] as List<AddonModel>;
var tags = results[2] as List<TagModel>;
var contacts = results[3] as List<BusinessMemberModel>;
var skills = results[4] as List<BusinessSkillModel>;
emit(state.copyWith(
inLoading: false,
hubs: hubs,
tags: tags,
addons: addons,
contacts: contacts,
skills: skills));
}
void _onChangeHub(
CreateEventChangeHub event, Emitter<CreateEventState> emit) async {
if (event.hub == state.entity.hub) return;
emit(state.copyWith(
entity: state.entity.copyWith(hub: event.hub),
));
state.shifts.forEach((element) {
element.bloc
.add(CreateShiftAddressSelectEvent(address: event.hub.fullAddress));
});
var departments =
await getIt<CreateEventRepository>().getDepartments(event.hub.id);
emit(state.copyWith(
departments: departments,
));
}
// void _onChangeContractType(
// CreateEventChangeContractType event, Emitter<CreateEventState> emit) {
// if (event.contractType == state.entity.contractType) return;
// emit(state.copyWith(
// entity: state.entity.copyWith(contractType: event.contractType),
// ));
// }
// void _onChangeContractNumber(
// CreateEventChangeContractNumber event, Emitter<CreateEventState> emit) {
// emit(state.copyWith(
// entity: state.entity.copyWith(contractNumber: event.contractNumber),
// ));
// }
//
void _onChangePoNumber(
CreateEventChangePoNumber event, Emitter<CreateEventState> emit) {
emit(state.copyWith(
entity: state.entity.copyWith(poNumber: event.poNumber),
));
}
void _onTagSelected(
CreateEventTagSelected event, Emitter<CreateEventState> emit) {
final tags = List<TagModel>.of(state.entity.tags ?? []);
if (tags.any((e) => e.id == event.tag.id)) {
tags.removeWhere((e) => e.id == event.tag.id);
} else {
tags.add(event.tag);
}
emit(state.copyWith(
entity: state.entity.copyWith(tags: tags),
));
}
void _onAddShift(CreateEventAddShift event, Emitter<CreateEventState> emit) {
final id = DateTime.now().millisecondsSinceEpoch.toString();
ShiftEntity newShiftEntity = ShiftEntity.empty();
final bloc = CreateShiftDetailsBloc(expanded: true)
..add(CreateShiftInitializeEvent(
newShiftEntity,
));
newShiftEntity.parentEvent = state.entity;
state.entity.shifts?.add(newShiftEntity);
emit(state.copyWith(
shifts: [
...state.shifts,
ShiftViewModel(id: id, bloc: bloc),
],
));
}
void _onRemoveShift(
CreateEventRemoveShift event, Emitter<CreateEventState> emit) {
emit(state.copyWith(
entity: state.entity.copyWith(
shifts: state.entity.shifts
?.where((element) => element.id != event.id)
.toList()),
shifts: state.shifts.where((element) => element.id != event.id).toList(),
));
}
void _onNameChange(
CreateEventNameChange event, Emitter<CreateEventState> emit) {
emit(state.copyWith(
entity: state.entity.copyWith(name: event.value),
));
}
void _onAddInfoChange(
CreateEventAddInfoChange event, Emitter<CreateEventState> emit) {
emit(state.copyWith(
entity: state.entity.copyWith(additionalInfo: event.value),
));
}
void _onToggleAddon(
CreateEventToggleAddon event, Emitter<CreateEventState> emit) {
final addons = List<AddonModel>.of(state.entity.addons ?? []);
if (addons.any((e) => e.id == event.addon.id)) {
addons.removeWhere((e) => e.id == event.addon.id);
} else {
addons.add(event.addon);
}
emit(state.copyWith(
entity: state.entity.copyWith(addons: addons),
));
}
void _onEntityUpdated(
CreateEventEntityUpdatedEvent event, Emitter<CreateEventState> emit) {
emit(state.copyWith());
}
void _onValidateAndPreview(
CreateEventValidateAndPreview event, Emitter<CreateEventState> emit) {
var newState = CreateEventInputValidator.validateInputs(
state.copyWith(entity: state.entity.copyWith()));
emit(newState);
emit(newState.copyWith(valid: false));
}
void _onDeleteDraft(
DeleteDraftEvent event, Emitter<CreateEventState> emit) async {
await getIt<CreateEventService>().deleteDraft(state.entity);
}
}

View File

@@ -0,0 +1,81 @@
part of 'create_event_bloc.dart';
@immutable
sealed class CreateEventEvent {}
class CreateEventInit extends CreateEventEvent {
final EventModel? eventModel;
final EventScheduleType? recurringType;
CreateEventInit(this.recurringType, this.eventModel);
}
// class CreateEventChangeContractType extends CreateEventEvent {
// final EventContractType contractType;
//
// CreateEventChangeContractType(this.contractType);
// }
class CreateEventChangeHub extends CreateEventEvent {
final HubModel hub;
CreateEventChangeHub(this.hub);
}
class CreateEventValidateAndPreview extends CreateEventEvent {
CreateEventValidateAndPreview();
}
// class CreateEventChangeContractNumber extends CreateEventEvent {
// final String contractNumber;
//
// CreateEventChangeContractNumber(this.contractNumber);
// }
class CreateEventChangePoNumber extends CreateEventEvent {
final String poNumber;
CreateEventChangePoNumber(this.poNumber);
}
class CreateEventNameChange extends CreateEventEvent {
final String value;
CreateEventNameChange(this.value);
}
class CreateEventAddInfoChange extends CreateEventEvent {
final String value;
CreateEventAddInfoChange(this.value);
}
class CreateEventTagSelected extends CreateEventEvent {
final TagModel tag;
CreateEventTagSelected(this.tag);
}
class CreateEventAddShift extends CreateEventEvent {
CreateEventAddShift();
}
class CreateEventRemoveShift extends CreateEventEvent {
final String id;
CreateEventRemoveShift(this.id);
}
class CreateEventToggleAddon extends CreateEventEvent {
final AddonModel addon;
CreateEventToggleAddon(this.addon);
}
class CreateEventEntityUpdatedEvent extends CreateEventEvent {
CreateEventEntityUpdatedEvent();
}
class DeleteDraftEvent extends CreateEventEvent {
DeleteDraftEvent();
}

View File

@@ -0,0 +1,138 @@
part of 'create_event_bloc.dart';
@immutable
class CreateEventState {
final bool inLoading;
final bool success;
final bool valid;
final EventEntity entity;
final EventScheduleType recurringType;
final List<HubModel> hubs;
final List<TagModel> tags;
final List<BusinessMemberModel> contacts;
final List<ShiftViewModel> shifts;
final List<AddonModel> addons;
final List<BusinessSkillModel> skills;
final List<DepartmentModel> departments;
final EventValidationState? validationState;
final bool showEmptyFieldError;
const CreateEventState(
{required this.entity,
this.inLoading = false,
this.valid = false,
this.showEmptyFieldError = false,
this.success = false,
this.recurringType = EventScheduleType.oneTime,
this.hubs = const [],
this.tags = const [],
this.contacts = const [],
this.addons = const [],
this.skills = const [],
this.shifts = const [],
this.departments = const [],
this.validationState});
CreateEventState copyWith({
bool? inLoading,
bool? success,
bool? valid,
bool? showEmptyFieldError,
EventEntity? entity,
EventScheduleType? recurringType,
List<HubModel>? hubs,
List<TagModel>? tags,
List<BusinessMemberModel>? contacts,
List<ShiftViewModel>? shifts,
List<AddonModel>? addons,
List<BusinessSkillModel>? skills,
List<DepartmentModel>? departments,
EventValidationState? validationState,
}) {
return CreateEventState(
success: success ?? this.success,
valid: valid ?? this.valid,
inLoading: inLoading ?? this.inLoading,
showEmptyFieldError: showEmptyFieldError ?? this.showEmptyFieldError,
entity: entity ?? this.entity,
recurringType: recurringType ?? this.recurringType,
hubs: hubs ?? this.hubs,
tags: tags ?? this.tags,
contacts: contacts ?? this.contacts,
shifts: shifts ?? this.shifts,
addons: addons ?? this.addons,
skills: skills ?? this.skills,
departments: departments ?? this.departments,
validationState: validationState,
);
}
}
class ShiftViewModel {
final String id;
final CreateShiftDetailsBloc bloc;
ShiftViewModel({required this.id, required this.bloc});
}
class EventValidationState {
final String? nameError;
final String? startDateError;
final String? endDateError;
final String? hubError;
// final String? contractNumberError;
final String? poNumberError;
final String? shiftsError;
bool showed = false;
bool get hasError =>
nameError != null ||
startDateError != null ||
endDateError != null ||
hubError != null ||
// contractNumberError != null ||
poNumberError != null ||
shiftsError != null;
String? get message {
return nameError ??
startDateError ??
endDateError ??
hubError ??
// contractNumberError ??
poNumberError ??
shiftsError ??
'';
}
EventValidationState(
{this.nameError,
this.startDateError,
this.endDateError,
this.hubError,
// this.contractNumberError,
this.poNumberError,
this.shiftsError});
EventValidationState copyWith({
String? nameError,
String? startDateError,
String? endDateError,
String? hubError,
// String? contractNumberError,
String? poNumberError,
String? shiftsError,
}) {
return EventValidationState(
nameError: nameError ?? this.nameError,
startDateError: startDateError ?? this.startDateError,
endDateError: endDateError ?? this.endDateError,
hubError: hubError ?? this.hubError,
// contractNumberError: contractNumberError ?? this.contractNumberError,
poNumberError: poNumberError ?? this.poNumberError,
shiftsError: shiftsError ?? this.shiftsError,
);
}
}

View File

@@ -0,0 +1,20 @@
import 'package:krow/core/data/models/event/addon_model.dart';
import 'package:krow/core/data/models/event/business_member_model.dart';
import 'package:krow/core/data/models/event/hub_model.dart';
import 'package:krow/core/data/models/event/tag_model.dart';
import 'package:krow/core/data/models/shift/business_skill_model.dart';
import 'package:krow/core/data/models/shift/department_model.dart';
abstract class CreateEventRepository {
Future<List<HubModel>> getHubs();
Future<List<AddonModel>> getAddons();
Future<List<TagModel>> getTags();
Future<List<BusinessMemberModel>> getContacts();
Future<List<BusinessSkillModel>> getSkills();
Future<List<DepartmentModel>> getDepartments(String hubId);
}

View File

@@ -0,0 +1,96 @@
import 'dart:convert';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:http/http.dart' as http;
import 'package:injectable/injectable.dart';
@singleton
class GooglePlacesService {
Future<List<MapPlace>> fetchSuggestions(String query) async {
final String apiKey = dotenv.env['GOOGLE_MAP']!;
const String baseUrl =
'https://maps.googleapis.com/maps/api/place/autocomplete/json';
final Uri uri =
Uri.parse('$baseUrl?input=$query&key=$apiKey&types=geocode');
final response = await http.get(uri);
if (response.statusCode == 200) {
final data = json.decode(response.body);
final List<dynamic> predictions = data['predictions'];
return predictions.map((prediction) {
return MapPlace.fromJson(prediction);
}).toList();
} else {
throw Exception('Failed to fetch place suggestions');
}
}
Future<Map<String, dynamic>> getPlaceDetails(String placeId) async {
final String apiKey = dotenv.env['GOOGLE_MAP']!;
final String url =
'https://maps.googleapis.com/maps/api/place/details/json?place_id=$placeId&key=$apiKey';
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
final data = json.decode(response.body);
final result = data['result'];
final location = result['geometry']['location'];
Map<String, dynamic> addressDetails = {
'lat': location['lat'], // Latitude
'lng': location['lng'], // Longitude
'formatted_address': result['formatted_address'], // Full Address
};
for (var component in result['address_components']) {
List types = component['types'];
if (types.contains('street_number')) {
addressDetails['street_number'] = component['long_name'];
}
if (types.contains('route')) {
addressDetails['street'] = component['long_name'];
}
if (types.contains('locality')) {
addressDetails['city'] = component['long_name'];
}
if (types.contains('administrative_area_level_1')) {
addressDetails['state'] = component['long_name'];
}
if (types.contains('country')) {
addressDetails['country'] = component['long_name'];
}
if (types.contains('postal_code')) {
addressDetails['postal_code'] = component['long_name'];
}
}
return addressDetails;
} else {
throw Exception('Failed to fetch place details');
}
}
}
class MapPlace {
final String description;
final String placeId;
MapPlace({required this.description, required this.placeId});
toJson() {
return {
'description': description,
'place_id': placeId,
};
}
factory MapPlace.fromJson(Map<String, dynamic> json) {
return MapPlace(
description: json['description'],
placeId: json['place_id'],
);
}
}

View File

@@ -0,0 +1,106 @@
import 'package:krow/core/data/models/event/event_model.dart';
import 'package:krow/features/create_event/domain/bloc/create_event_bloc.dart';
import 'package:krow/features/create_event/presentation/create_role_section/bloc/create_role_bloc.dart';
import 'package:krow/features/create_event/presentation/create_shift_details_section/bloc/create_shift_details_bloc.dart';
class CreateEventInputValidator {
static CreateEventState validateInputs(CreateEventState state) {
var newState = _validateEvent(state);
newState = _validateShifts(newState);
return newState;
}
static CreateEventState _validateEvent(CreateEventState state) {
EventValidationState? validationState;
if (state.entity.name.isEmpty) {
validationState = EventValidationState(nameError: 'Name cannot be empty');
}
if (state.entity.startDate == null) {
validationState =
EventValidationState(startDateError: 'Start Date cannot be empty');
}
if (state.recurringType == EventScheduleType.recurring &&
state.entity.endDate == null) {
validationState =
EventValidationState(endDateError: 'End Date cannot be empty');
}
if (state.entity.hub == null) {
validationState = EventValidationState(hubError: 'Hub cannot be empty');
}
// if (state.entity.contractType == EventContractType.contract &&
// (state.entity.contractNumber?.isEmpty ?? true)) {
// validationState = EventValidationState(
// contractNumberError: 'Contract Number cannot be empty');
// }
if ((state.entity.poNumber?.isEmpty ?? true)) {
validationState =
EventValidationState(poNumberError: 'PO Number cannot be empty');
}
if (state.shifts.isEmpty) {
validationState =
EventValidationState(shiftsError: 'Shifts cannot be empty');
}
if (state.validationState == null && validationState == null) {
return state.copyWith(valid: true);
} else {
return state.copyWith(validationState: validationState);
}
}
static CreateEventState _validateShifts(CreateEventState state) {
for (var shift in state.shifts) {
ShiftValidationState? validationState;
if (!(shift.bloc.state.shift.fullAddress?.isValid() ?? false)) {
validationState = ShiftValidationState(addressError: 'Invalid Address');
}
if (shift.bloc.state.shift.managers.isEmpty) {
validationState =
ShiftValidationState(contactsError: 'Managers cannot be empty');
}
if (validationState != null) {
shift.bloc.add(ValidationFailedEvent(validationState));
return state.copyWith(
valid: false, validationState: state.validationState);
}
if (validatePosition(shift.bloc.state.roles)) {
return state.copyWith(
valid: false, validationState: state.validationState);
}
}
return state;
}
static bool validatePosition(List<RoleViewModel> roles) {
for (var position in roles) {
PositionValidationState? validationState;
if (position.bloc.state.entity.businessSkill == null) {
validationState =
PositionValidationState(skillError: 'Skill cannot be empty');
}
if (position.bloc.state.entity.department == null) {
validationState = PositionValidationState(
departmentError: 'Department cannot be empty');
}
if (position.bloc.state.entity.count == null) {
validationState =
PositionValidationState(countError: 'Count cannot be empty');
}
if (validationState != null) {
position.bloc.add(ValidationPositionFailedEvent(validationState));
return true;
}
}
return false;
}
}