feat: legacy mobile apps created
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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});
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:krow/core/entity/event_entity.dart';
|
||||
|
||||
abstract class EventsRepository {
|
||||
Stream<EventStatus> get statusStream;
|
||||
|
||||
Future<List<EventEntity>> getEvents(
|
||||
{String? lastItemId, required EventStatus statusFilter});
|
||||
|
||||
Future<EventEntity?> getEventById(String id);
|
||||
|
||||
void dispose();
|
||||
|
||||
Future<void> cancelClientEvent(String id, EventStatus? status);
|
||||
|
||||
Future<void> completeClientEvent(String id, {String? comment});
|
||||
|
||||
Future<void> trackClientClockin(String positionStaffId);
|
||||
|
||||
Future<void> trackClientClockout(String positionStaffId);
|
||||
|
||||
Future<void> notShowedPositionStaff(String positionStaffId);
|
||||
|
||||
Future<void> replacePositionStaff(String positionStaffId, String reason);
|
||||
|
||||
void refreshEvents(EventStatus status);
|
||||
}
|
||||
Reference in New Issue
Block a user