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,381 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:collection/collection.dart';
import 'package:krow/core/application/clients/api/api_exception.dart';
import 'package:krow/core/application/di/injectable.dart';
import 'package:krow/core/data/models/staff/pivot.dart';
import 'package:krow/core/entity/event_entity.dart';
import 'package:krow/core/entity/position_entity.dart';
import 'package:krow/core/entity/shift_entity.dart';
import 'package:krow/core/entity/staff_contact_entity.dart';
import 'package:krow/core/sevices/create_event_service/create_event_service.dart';
import 'package:krow/core/sevices/geofencin_service.dart';
import 'package:krow/features/events/domain/events_repository.dart';
import 'package:meta/meta.dart';
part 'event_details_event.dart';
part 'event_details_state.dart';
class EventDetailsBloc extends Bloc<EventDetailsEvent, EventDetailsState> {
final EventsRepository _repository;
Timer? _timer;
var disabledPolling = false;
EventDetailsBloc(EventEntity event, this._repository, isPreview)
: super(EventDetailsState(event: event, isPreview: isPreview)) {
on<EventDetailsInitialEvent>(_onInit);
on<EventDetailsUpdateEvent>(_onUpdateEntity);
on<OnShiftHeaderTapEvent>(_onShiftHeaderTap);
on<OnRoleHeaderTapEvent>(_onRoleHeaderTap);
on<OnAssignedStaffHeaderTapEvent>(_onAssignedStaffHeaderTap);
on<CompleteEventEvent>(_onCompleteEvent);
on<CancelClientEvent>(_onCancelClientEvent);
on<TrackClientClockin>(_onTrackClientClockin);
on<TrackClientClockout>(_onTrackClientClockout);
on<NotShowedPositionStaffEvent>(_onNotShowedPositionStaff);
on<ReplacePositionStaffEvent>(_onReplacePositionStaff);
on<CreateEventPostEvent>(_createEvent);
on<DetailsDeleteDraftEvent>(_onDetailsDeleteDraftEvent);
on<DetailsPublishEvent>(_onDetailsPublishEvent);
on<GeofencingEvent>(_onGeofencingEvent);
on<RefreshEventDetailsEvent>(_onRefreshEventDetailsEvent);
on<DisablePollingEvent>(_onDisablePollingEvent);
on<EnablePollingEvent>(_onEnablePollingEvent);
}
@override
Future<void> close() {
_timer?.cancel();
return super.close();
}
FutureOr<void> _onShiftHeaderTap(OnShiftHeaderTapEvent event, emit) {
emit(state.copyWith(
shifts: state.shifts
.map((e) =>
e.copyWith(isExpanded: e == event.shiftState && !e.isExpanded))
.toList()));
}
FutureOr<void> _onRoleHeaderTap(OnRoleHeaderTapEvent event, emit) {
emit(state.copyWith(
shifts: state.shifts
.map((e) => e.copyWith(
positions: e.positions
.map((r) => r.copyWith(
isExpanded:
r.position.id == event.roleState.position.id &&
!r.isExpanded))
.toList()))
.toList()));
}
FutureOr<void> _onAssignedStaffHeaderTap(
OnAssignedStaffHeaderTapEvent event, emit) {
emit(state.copyWith(
shifts: state.shifts
.map((e) => e.copyWith(
positions: e.positions
.map((r) => r.copyWith(
isStaffExpanded:
r.position.id == event.roleState.position.id &&
!r.isStaffExpanded))
.toList()))
.toList()));
}
FutureOr<void> _onCompleteEvent(CompleteEventEvent event, emit) async {
try {
emit(state.copyWith(inLoading: true));
await _repository.completeClientEvent(state.event.id,
comment: event.comment);
emit(state.copyWith(
event: state.event.copyWith(status: EventStatus.completed)));
} catch (e) {
if (e is DisplayableException) {
emit(state.copyWith(showErrorPopup: e.message));
return;
}
}
emit(state.copyWith(inLoading: false));
}
FutureOr<void> _onCancelClientEvent(CancelClientEvent event, emit) async {
emit(state.copyWith(inLoading: true));
try {
await _repository.cancelClientEvent(state.event.id, state.event.status);
emit(state.copyWith(
event: state.event.copyWith(status: EventStatus.canceled)));
} catch (e) {
if (e is DisplayableException) {
emit(state.copyWith(showErrorPopup: e.message));
}
}
emit(state.copyWith(inLoading: false));
}
FutureOr<void> _onInit(EventDetailsInitialEvent event, emit) {
getIt<CreateEventService>().eventsRepository = _repository;
emit(state.copyWith(
inLoading: true,
shifts: state.event.shifts
?.map((shift) => ShiftState(
shift: shift,
positions: shift.positions
.map((position) => PositionState(position: position))
.toList()))
.toList() ??
[],
));
add(RefreshEventDetailsEvent());
if (!state.isPreview) {
startPoling();
}else{
emit(state.copyWith(inLoading: false));
}
}
FutureOr<void> _onTrackClientClockin(TrackClientClockin event, emit) async {
emit(state.copyWith(inLoading: true));
if (!(await checkGeofancing(event.staffContact))) {
emit(state.copyWith(inLoading: false));
return;
}
try {
await _repository.trackClientClockin(event.staffContact.id);
event.staffContact.status = PivotStatus.ongoing;
} catch (e) {
if (e is DisplayableException) {
emit(state.copyWith(showErrorPopup: e.message));
}
}
emit(state.copyWith(inLoading: false));
}
FutureOr<void> _onTrackClientClockout(TrackClientClockout event, emit) async {
emit(state.copyWith(inLoading: true));
try {
await _repository.trackClientClockout(event.staffContact.id);
event.staffContact.status = PivotStatus.confirmed;
} catch (e) {
if (e is DisplayableException) {
emit(state.copyWith(showErrorPopup: e.message));
}
}
emit(state.copyWith(inLoading: false));
}
FutureOr<void> _onNotShowedPositionStaff(
NotShowedPositionStaffEvent event, emit) async {
emit(state.copyWith(inLoading: true));
try {
await _repository.notShowedPositionStaff(event.positionStaffId);
} catch (e) {
if (e is DisplayableException) {
emit(state.copyWith(showErrorPopup: e.message));
}
}
emit(state.copyWith(inLoading: false));
}
FutureOr<void> _onReplacePositionStaff(
ReplacePositionStaffEvent event, emit) async {
emit(state.copyWith(inLoading: true));
try {
await _repository.replacePositionStaff(
event.positionStaffId, event.reason);
} catch (e) {
if (e is DisplayableException) {
emit(state.copyWith(showErrorPopup: e.message));
}else{
emit(state.copyWith(
showErrorPopup: 'Something went wrong'));
}
}
emit(state.copyWith(inLoading: false));
}
void _createEvent(
CreateEventPostEvent event, Emitter<EventDetailsState> emit) async {
emit(state.copyWith(inLoading: true));
try {
if (state.event.id.isEmpty) {
await getIt<CreateEventService>().createEventService(
state.event,
);
} else {
await getIt<CreateEventService>().updateEvent(
state.event,
);
}
// emit(state.copyWith(inLoading: false, ));
emit(state.copyWith(inLoading: false, needDeepPop: true));
} catch (e) {
if (e is DisplayableException) {
emit(state.copyWith(inLoading: false, showErrorPopup: e.message));
return;
} else {
print(e);
emit(state.copyWith(
inLoading: false, showErrorPopup: 'Something went wrong'));
return;
}
}
emit(state.copyWith(inLoading: false));
}
void _onDetailsDeleteDraftEvent(DetailsDeleteDraftEvent event, emit) async {
emit(state.copyWith(inLoading: true));
try {
await getIt<CreateEventService>().deleteDraft(
state.event,
);
emit(state.copyWith(inLoading: false, needDeepPop: true));
} catch (e) {
if (e is DisplayableException) {
emit(state.copyWith(inLoading: false, showErrorPopup: e.message));
return;
} else {
emit(state.copyWith(
inLoading: false, showErrorPopup: 'Something went wrong'));
}
}
emit(state.copyWith(inLoading: false));
}
void _onDetailsPublishEvent(DetailsPublishEvent event, emit) async {
emit(state.copyWith(inLoading: true));
try {
await getIt<CreateEventService>().publishEvent(
state.event,
);
emit(state.copyWith(inLoading: false, needDeepPop: true));
} catch (e) {
if (e is DisplayableException) {
emit(state.copyWith(inLoading: false, showErrorPopup: e.message));
return;
} else {
print(e);
emit(state.copyWith(
inLoading: false, showErrorPopup: 'Something went wrong'));
}
}
emit(state.copyWith(inLoading: false));
}
void startPoling() {
_timer = Timer.periodic(const Duration(seconds: 5), (timer) async {
if(state.isPreview) return;
add(RefreshEventDetailsEvent());
});
}
void _onRefreshEventDetailsEvent(
RefreshEventDetailsEvent event, Emitter<EventDetailsState> emit) async {
if(state.isPreview) return;
try {
await _repository.getEventById(state.event.id).then((event) {
if (event != null) {
add(EventDetailsUpdateEvent(event));
}
});
} catch (e) {
print(e);
}
}
void _onUpdateEntity(EventDetailsUpdateEvent event, emit) {
if(disabledPolling) return;
emit(
state.copyWith(
inLoading: false,
event: event.event,
shifts: event.event.shifts?.map(
(shift) {
var oldShift = state.shifts
.firstWhereOrNull((e) => e.shift.id == shift.id);
return ShiftState(
isExpanded: oldShift?.isExpanded ?? false,
shift: shift,
positions: shift.positions.map(
(position) {
var oldPosition = oldShift?.positions.firstWhereOrNull(
(e) => e.position.id == position.id);
return PositionState(
position: position,
isExpanded: oldPosition?.isExpanded ?? false,
isStaffExpanded:
oldPosition?.isStaffExpanded ?? false);
},
).toList());
},
).toList() ??
[],
),
);
}
Future<bool> checkGeofancing(StaffContact staffContact) async {
var permissionResult =
await GeofencingService().requestGeolocationPermission();
if (permissionResult == GeolocationStatus.enabled) {
var result = await GeofencingService().isInRangeCheck(
pointLatitude:
staffContact.parentPosition?.parentShift?.fullAddress?.latitude ??
0,
pointLongitude:
staffContact.parentPosition?.parentShift?.fullAddress?.longitude ??
0,
);
if (result) {
return true;
} else {
add(GeofencingEvent(GeofencingDialogState.tooFar));
}
} else if (permissionResult == GeolocationStatus.disabled) {
add(GeofencingEvent(GeofencingDialogState.locationDisabled));
} else if (permissionResult == GeolocationStatus.prohibited) {
add(GeofencingEvent(GeofencingDialogState.goToSettings));
} else if (permissionResult == GeolocationStatus.denied) {
add(GeofencingEvent(GeofencingDialogState.permissionDenied));
}
return false;
}
void _onGeofencingEvent(GeofencingEvent event, emit) {
emit(state.copyWith(geofencingDialogState: event.dialogState));
if (event.dialogState != GeofencingDialogState.none) {
add(GeofencingEvent(GeofencingDialogState.none));
}
}
void _onDisablePollingEvent(DisablePollingEvent event, emit) {
disabledPolling = true;
_timer?.cancel();
_timer = null;
}
void _onEnablePollingEvent(EnablePollingEvent event, emit) {
disabledPolling = false;
if (_timer == null) {
startPoling();
}
}
@override
void onEvent(EventDetailsEvent event) {
print(event);
super.onEvent(event);
}
}

View File

@@ -0,0 +1,105 @@
part of 'event_details_bloc.dart';
@immutable
abstract class EventDetailsEvent {}
class EventDetailsInitialEvent extends EventDetailsEvent {
EventDetailsInitialEvent();
}
class EventDetailsUpdateEvent extends EventDetailsEvent {
final EventEntity event;
EventDetailsUpdateEvent(this.event);
}
class OnShiftHeaderTapEvent extends EventDetailsEvent {
final ShiftState shiftState;
OnShiftHeaderTapEvent(this.shiftState);
}
class OnRoleHeaderTapEvent extends EventDetailsEvent {
final PositionState roleState;
OnRoleHeaderTapEvent(this.roleState);
}
class OnAssignedStaffHeaderTapEvent extends EventDetailsEvent {
final PositionState roleState;
OnAssignedStaffHeaderTapEvent(this.roleState);
}
class CompleteEventEvent extends EventDetailsEvent {
final String? comment;
CompleteEventEvent({this.comment});
}
class CancelClientEvent extends EventDetailsEvent {
CancelClientEvent();
}
class CompleteClientEvent extends EventDetailsEvent {
final String id;
final String? comment;
CompleteClientEvent(this.id, {this.comment});
}
class TrackClientClockin extends EventDetailsEvent {
final StaffContact staffContact;
TrackClientClockin(this.staffContact);
}
class TrackClientClockout extends EventDetailsEvent {
final StaffContact staffContact;
TrackClientClockout(this.staffContact);
}
class NotShowedPositionStaffEvent extends EventDetailsEvent {
final String positionStaffId;
NotShowedPositionStaffEvent(this.positionStaffId);
}
class ReplacePositionStaffEvent extends EventDetailsEvent {
final String positionStaffId;
final String reason;
ReplacePositionStaffEvent(this.positionStaffId, this.reason);
}
class CreateEventPostEvent extends EventDetailsEvent {
CreateEventPostEvent();
}
class DetailsDeleteDraftEvent extends EventDetailsEvent {
DetailsDeleteDraftEvent();
}
class DetailsPublishEvent extends EventDetailsEvent {
DetailsPublishEvent();
}
class GeofencingEvent extends EventDetailsEvent {
final GeofencingDialogState dialogState;
GeofencingEvent(this.dialogState);
}
class RefreshEventDetailsEvent extends EventDetailsEvent {
RefreshEventDetailsEvent();
}
class DisablePollingEvent extends EventDetailsEvent {
DisablePollingEvent();
}
class EnablePollingEvent extends EventDetailsEvent {
EnablePollingEvent();
}

View File

@@ -0,0 +1,99 @@
part of 'event_details_bloc.dart';
enum GeofencingDialogState {
none,
tooFar,
locationDisabled,
goToSettings,
permissionDenied,
}
@immutable
class EventDetailsState {
final EventEntity event;
final List<ShiftState> shifts;
final bool inLoading;
final bool isPreview;
final String? showErrorPopup;
final bool needDeepPop;
final GeofencingDialogState geofencingDialogState;
const EventDetailsState({
required this.event,
this.shifts = const [],
this.inLoading = false,
this.isPreview = false,
this.needDeepPop = false,
this.showErrorPopup,
this.geofencingDialogState = GeofencingDialogState.none,
});
EventDetailsState copyWith({
EventEntity? event,
List<ShiftState>? shifts,
bool? inLoading,
bool? isPreview,
bool? needDeepPop,
String? showErrorPopup,
GeofencingDialogState? geofencingDialogState,
}) {
return EventDetailsState(
event: event ?? this.event,
shifts: shifts ?? this.shifts,
inLoading: inLoading ?? this.inLoading,
isPreview: isPreview ?? this.isPreview,
needDeepPop: needDeepPop ?? this.needDeepPop,
showErrorPopup: showErrorPopup,
geofencingDialogState:
geofencingDialogState ?? GeofencingDialogState.none,
);
}
}
class ShiftState {
final bool isExpanded;
final ShiftEntity shift;
final List<PositionState> positions;
ShiftState({
this.isExpanded = false,
required this.shift,
this.positions = const [],
});
ShiftState copyWith({
bool? isExpanded,
ShiftEntity? shift,
List<PositionState>? positions,
DateTime? date,
}) {
return ShiftState(
isExpanded: isExpanded ?? this.isExpanded,
shift: shift ?? this.shift,
positions: positions ?? this.positions,
);
}
}
class PositionState {
final PositionEntity position;
final bool isExpanded;
final bool isStaffExpanded;
PositionState(
{required this.position,
this.isExpanded = false,
this.isStaffExpanded = false});
PositionState copyWith({
PositionEntity? position,
bool? isExpanded,
bool? isStaffExpanded,
}) {
return PositionState(
position: position ?? this.position,
isExpanded: isExpanded ?? this.isExpanded,
isStaffExpanded: isStaffExpanded ?? this.isStaffExpanded,
);
}
}

View File

@@ -0,0 +1,185 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow/core/application/di/injectable.dart';
import 'package:krow/core/entity/event_entity.dart';
import 'package:krow/features/events/domain/blocs/events_list_bloc/events_event.dart';
import 'package:krow/features/events/domain/blocs/events_list_bloc/events_state.dart';
import 'package:krow/features/events/domain/events_repository.dart';
class EventsBloc extends Bloc<EventsEvent, EventsState> {
EventsBloc()
: super(const EventsState(tabs: {
0: {
0: EventTabState(
items: [],
inLoading: false,
hasMoreItems: true,
status: EventStatus.pending),
1: EventTabState(
items: [],
inLoading: false,
hasMoreItems: true,
status: EventStatus.assigned),
2: EventTabState(
items: [],
inLoading: false,
hasMoreItems: true,
status: EventStatus.confirmed),
},
1: {
0: EventTabState(
items: [],
inLoading: false,
hasMoreItems: true,
status: EventStatus.active),
1: EventTabState(
items: [],
inLoading: false,
hasMoreItems: true,
status: EventStatus.finished),
},
2: {
0: EventTabState(
items: [],
inLoading: false,
hasMoreItems: true,
status: EventStatus.completed),
1: EventTabState(
items: [],
inLoading: false,
hasMoreItems: true,
status: EventStatus.closed),
2: EventTabState(
items: [],
inLoading: false,
hasMoreItems: true,
status: EventStatus.canceled),
},
3: {
0: EventTabState(
items: [],
inLoading: false,
hasMoreItems: true,
status: EventStatus.draft),
},
})) {
on<EventsInitialEvent>(_onInitial);
on<EventsTabChangedEvent>(_onTabChanged);
on<EventsSubTabChangedEvent>(_onSubTabChanged);
on<LoadTabEventEvent>(_onLoadTabItems);
on<LoadMoreEventEvent>(_onLoadMoreTabItems);
getIt<EventsRepository>().statusStream.listen((event) {
(int, int)? pair = findTabIndexForStatus(state.tabs, event);
if (pair != null) {
add(LoadTabEventEvent(tabIndex: pair.$1, subTabIndex: pair.$2));
}
});
}
Future<void> _onInitial(EventsInitialEvent event, emit) async {
add(const LoadTabEventEvent(tabIndex: 0, subTabIndex: 0));
}
Future<void> _onTabChanged(EventsTabChangedEvent event, emit) async {
emit(state.copyWith(tabIndex: event.tabIndex, subTabIndex: 0));
final currentTabState = state.tabs[event.tabIndex]![0]!;
if (currentTabState.items.isEmpty && !currentTabState.inLoading) {
add(LoadTabEventEvent(tabIndex: event.tabIndex, subTabIndex: 0));
}
}
Future<void> _onSubTabChanged(EventsSubTabChangedEvent event, emit) async {
emit(state.copyWith(subTabIndex: event.subTabIndex));
final currentTabState = state.tabs[state.tabIndex]![event.subTabIndex]!;
if (currentTabState.items.isEmpty && !currentTabState.inLoading) {
add(LoadTabEventEvent(
tabIndex: state.tabIndex, subTabIndex: event.subTabIndex));
}
}
Future<void> _onLoadTabItems(LoadTabEventEvent event, emit) async {
await _fetchEvents(event.tabIndex, event.subTabIndex, null, emit);
}
Future<void> _onLoadMoreTabItems(LoadMoreEventEvent event, emit) async {
final currentTabState = state.tabs[event.tabIndex]![event.subTabIndex]!;
if (!currentTabState.hasMoreItems || currentTabState.inLoading) return;
await _fetchEvents(
event.tabIndex, event.subTabIndex, currentTabState.items, emit);
}
_fetchEvents(int tabIndex, int subTabIndex, List<EventEntity>? previousItems,
emit) async {
if (previousItems != null && previousItems.lastOrNull?.cursor == null) {
return;
}
final currentTabState = state.tabs[tabIndex]![subTabIndex]!;
var newState = state.copyWith(
tabs: {
...state.tabs,
tabIndex: {
...state.tabs[tabIndex]!,
subTabIndex: currentTabState.copyWith(inLoading: true)
},
},
);
emit(newState);
await Future.delayed(const Duration(seconds: 1));
try {
var items = await getIt<EventsRepository>().getEvents(
statusFilter: currentTabState.status,
lastItemId: previousItems?.lastOrNull?.cursor,
);
var newState = state.copyWith(
tabs: {
...state.tabs,
tabIndex: {
...state.tabs[tabIndex]!,
subTabIndex: currentTabState.copyWith(
items: (previousItems ?? [])..addAll(items),
hasMoreItems: items.isNotEmpty,
inLoading: false,
)
},
},
);
emit(newState);
} catch (e) {
debugPrint(e.toString());
emit(state.copyWith(errorMessage: e.toString()));
emit(state.copyWith(
tabs: {
...state.tabs,
tabIndex: {
...state.tabs[tabIndex]!,
subTabIndex: currentTabState.copyWith(
items: (previousItems ?? []),
hasMoreItems: false,
inLoading: false,
)
},
},
));
}
}
@override
Future<void> close() {
getIt<EventsRepository>().dispose();
return super.close();
}
(int, int)? findTabIndexForStatus(
Map<int, Map<int, EventTabState>> tabs, EventStatus status) {
for (var tabEntry in tabs.entries) {
for (var subTabEntry in tabEntry.value.entries) {
if (subTabEntry.value.status == status) {
return (tabEntry.key, subTabEntry.key);
}
}
}
return null;
}
}

View File

@@ -0,0 +1,33 @@
sealed class EventsEvent {
const EventsEvent();
}
class EventsInitialEvent extends EventsEvent {
const EventsInitialEvent();
}
class EventsTabChangedEvent extends EventsEvent {
final int tabIndex;
const EventsTabChangedEvent({required this.tabIndex});
}
class EventsSubTabChangedEvent extends EventsEvent {
final int subTabIndex;
const EventsSubTabChangedEvent({required this.subTabIndex});
}
class LoadTabEventEvent extends EventsEvent {
final int tabIndex;
final int subTabIndex;
const LoadTabEventEvent({required this.tabIndex, required this.subTabIndex});
}
class LoadMoreEventEvent extends EventsEvent {
final int tabIndex;
final int subTabIndex;
const LoadMoreEventEvent({required this.tabIndex, required this.subTabIndex});
}

View File

@@ -0,0 +1,61 @@
import 'package:krow/core/entity/event_entity.dart';
class EventsState {
final bool inLoading;
final int tabIndex;
final int subTabIndex;
final String? errorMessage;
final Map<int, Map<int, EventTabState>> tabs;
const EventsState(
{this.inLoading = false,
this.tabIndex = 0,
this.subTabIndex = 0,
this.errorMessage,
required this.tabs});
EventsState copyWith({
bool? inLoading,
int? tabIndex,
int? subTabIndex,
String? errorMessage,
Map<int, Map<int, EventTabState>>? tabs,
}) {
return EventsState(
inLoading: inLoading ?? this.inLoading,
tabIndex: tabIndex ?? this.tabIndex,
subTabIndex: subTabIndex ?? this.subTabIndex,
tabs: tabs ?? this.tabs,
errorMessage: errorMessage,
);
}
}
class EventTabState {
final List<EventEntity> items;
final bool inLoading;
final bool hasMoreItems;
final EventStatus status;
const EventTabState({
required this.items,
this.inLoading = false,
this.hasMoreItems = true,
required this.status,
});
EventTabState copyWith({
List<EventEntity>? items,
bool? inLoading,
bool? hasMoreItems,
}) {
return EventTabState(
items: items ?? this.items,
inLoading: inLoading ?? this.inLoading,
hasMoreItems: hasMoreItems ?? this.hasMoreItems,
status: status,
);
}
}