feat: legacy mobile apps created
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow/core/application/common/date_time_extension.dart';
|
||||
import 'package:krow/features/shifts/domain/blocs/complete_dialog/shift_complete_dialog_event.dart';
|
||||
import 'package:krow/features/shifts/domain/blocs/complete_dialog/shift_complete_dialog_state.dart';
|
||||
|
||||
class CompleteDialogBloc
|
||||
extends Bloc<CompleteDialogEvent, CompleteDialogState> {
|
||||
CompleteDialogBloc() : super(const CompleteDialogState()) {
|
||||
on<InitializeCompleteDialog>((event, emit) {
|
||||
|
||||
emit(state.copyWith(
|
||||
minLimit: event.minLimit,
|
||||
startTime: event.minLimit?.toHourMinuteString() ?? DateTime.now().toHourMinuteString(),
|
||||
endTime: event.minLimit?.add(Duration(minutes: event.breakDurationInMinutes)).toHourMinuteString() ?? DateTime.now().add(Duration(minutes: event.breakDurationInMinutes)).toHourMinuteString(),
|
||||
breakDuration: Duration(minutes: event.breakDurationInMinutes),
|
||||
));
|
||||
});
|
||||
|
||||
on<SelectBreakStatus>((event, emit) {
|
||||
emit(state.copyWith(status: event.status));
|
||||
});
|
||||
|
||||
on<SelectReason>((event, emit) {
|
||||
emit(state.copyWith(selectedReason: event.reason));
|
||||
});
|
||||
|
||||
on<ChangeStartTime>((event, emit) {
|
||||
emit(state.copyWith(startTime: event.startTime.toHourMinuteString(),endTime: event.startTime.add(state.breakDuration).toHourMinuteString()));
|
||||
});
|
||||
|
||||
on<ChangeEndTime>((event, emit) {
|
||||
emit(state.copyWith(endTime: event.endTime.toHourMinuteString()));
|
||||
});
|
||||
|
||||
on<ChangeAdditionalReason>((event, emit) {
|
||||
emit(state.copyWith(additionalReason: event.additionalReason));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow/features/shifts/domain/blocs/complete_dialog/shift_complete_dialog_state.dart';
|
||||
|
||||
abstract class CompleteDialogEvent extends Equatable {
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class InitializeCompleteDialog extends CompleteDialogEvent {
|
||||
final DateTime? minLimit;
|
||||
final int breakDurationInMinutes;
|
||||
|
||||
InitializeCompleteDialog(
|
||||
{this.minLimit, required this.breakDurationInMinutes});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [minLimit];
|
||||
}
|
||||
|
||||
class SelectBreakStatus extends CompleteDialogEvent {
|
||||
final BreakStatus status;
|
||||
|
||||
SelectBreakStatus(this.status);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status];
|
||||
}
|
||||
|
||||
class SelectReason extends CompleteDialogEvent {
|
||||
final String reason;
|
||||
|
||||
SelectReason(this.reason);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [reason];
|
||||
}
|
||||
|
||||
class ChangeStartTime extends CompleteDialogEvent {
|
||||
final DateTime startTime;
|
||||
|
||||
ChangeStartTime(this.startTime);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [startTime];
|
||||
}
|
||||
|
||||
class ChangeEndTime extends CompleteDialogEvent {
|
||||
final DateTime endTime;
|
||||
|
||||
ChangeEndTime(this.endTime);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [endTime];
|
||||
}
|
||||
|
||||
class ChangeAdditionalReason extends CompleteDialogEvent {
|
||||
final String additionalReason;
|
||||
|
||||
ChangeAdditionalReason(this.additionalReason);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [additionalReason];
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
enum BreakStatus { neutral, positive, negative }
|
||||
|
||||
class CompleteDialogState extends Equatable {
|
||||
final BreakStatus status;
|
||||
final String? selectedReason;
|
||||
final String startTime;
|
||||
final String endTime;
|
||||
final String additionalReason;
|
||||
final DateTime? minLimit;
|
||||
final Duration breakDuration;
|
||||
|
||||
const CompleteDialogState({
|
||||
this.status = BreakStatus.neutral,
|
||||
this.selectedReason,
|
||||
this.startTime = '00:00',
|
||||
this.endTime = '00:00',
|
||||
this.additionalReason = '',
|
||||
this.minLimit,
|
||||
this.breakDuration = const Duration(minutes: 0),
|
||||
});
|
||||
|
||||
String? get breakTimeInputError {
|
||||
if (startTime == endTime) {
|
||||
return 'Break start time and end time cannot be the same';
|
||||
}
|
||||
if (DateFormat('H:mm')
|
||||
.parse(startTime)
|
||||
.isAfter(DateFormat('H:mm').parse(endTime))) {
|
||||
return 'Break start time cannot be after break end time';
|
||||
}
|
||||
if (DateFormat('H:mm').parse(endTime).isAfter(DateTime.now())) {
|
||||
return 'Break end time cannot be in the future';
|
||||
}
|
||||
|
||||
if (minLimit != null) {
|
||||
final start = DateFormat('H:mm').parse(startTime);
|
||||
final min =
|
||||
DateFormat('H:mm').parse(DateFormat('H:mm').format(minLimit!));
|
||||
if (start.isBefore(min)) {
|
||||
return 'Break start time cannot be before the shift start time';
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
CompleteDialogState copyWith({
|
||||
BreakStatus? status,
|
||||
String? selectedReason,
|
||||
String? startTime,
|
||||
String? endTime,
|
||||
String? additionalReason,
|
||||
DateTime? minLimit,
|
||||
Duration? breakDuration,
|
||||
}) {
|
||||
return CompleteDialogState(
|
||||
status: status ?? this.status,
|
||||
selectedReason: selectedReason ?? this.selectedReason,
|
||||
startTime: startTime ?? this.startTime,
|
||||
endTime: endTime ?? this.endTime,
|
||||
additionalReason: additionalReason ?? this.additionalReason,
|
||||
minLimit: minLimit ?? this.minLimit,
|
||||
breakDuration: breakDuration ?? this.breakDuration,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props =>
|
||||
[status, selectedReason, startTime, endTime, additionalReason];
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow/core/application/clients/api/api_exception.dart';
|
||||
import 'package:krow/core/application/di/injectable.dart';
|
||||
import 'package:krow/core/sevices/geofencing_serivce.dart';
|
||||
import 'package:krow/features/shifts/data/models/staff_shift.dart';
|
||||
import 'package:krow/features/shifts/domain/services/force_clockout_service.dart';
|
||||
import 'package:krow/features/shifts/domain/shift_entity.dart';
|
||||
import 'package:krow/features/shifts/domain/shifts_repository.dart';
|
||||
|
||||
part 'shift_details_event.dart';
|
||||
part 'shift_details_state.dart';
|
||||
|
||||
class ShiftDetailsBloc extends Bloc<ShiftDetailsEvent, ShiftDetailsState> {
|
||||
ShiftDetailsBloc()
|
||||
: super(ShiftDetailsState(shiftViewModel: ShiftEntity.empty)) {
|
||||
on<ShiftDetailsInitialEvent>(_onInitial);
|
||||
on<StaffGeofencingUpdate>(_onStaffGeofencingUpdate);
|
||||
on<ShiftUpdateTimerEvent>(_onUpdateTimer);
|
||||
on<ShiftConfirmEvent>(_onConfirm);
|
||||
on<ShiftClockInEvent>(_onClockIn);
|
||||
on<ShiftCompleteEvent>(_onComplete);
|
||||
on<ShiftDeclineEvent>(_onDecline);
|
||||
on<ShiftCancelEvent>(_onCancel);
|
||||
on<ShiftRefreshEvent>(_onRefresh);
|
||||
on<ShiftCheckGeocodingEvent>(_onCheckGeocoding);
|
||||
on<ShiftErrorWasShownEvent>(_onErrorWasShown);
|
||||
}
|
||||
|
||||
final GeofencingService _geofencingService = getIt<GeofencingService>();
|
||||
Timer? _timer;
|
||||
Timer? _refreshTimer;
|
||||
StreamSubscription<bool>? _geofencingStream;
|
||||
|
||||
Future<void> _onInitial(
|
||||
ShiftDetailsInitialEvent event,
|
||||
Emitter<ShiftDetailsState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(shiftViewModel: event.shift));
|
||||
if (event.shift.status == EventShiftRoleStaffStatus.ongoing) {
|
||||
_startOngoingTimer();
|
||||
}
|
||||
_runRefreshTimer();
|
||||
|
||||
add(const ShiftCheckGeocodingEvent());
|
||||
}
|
||||
|
||||
void _onStaffGeofencingUpdate(
|
||||
StaffGeofencingUpdate event,
|
||||
Emitter<ShiftDetailsState> emit,
|
||||
) {
|
||||
emit(state.copyWith(isToFar: !event.isInRange));
|
||||
}
|
||||
|
||||
void _onUpdateTimer(
|
||||
ShiftUpdateTimerEvent event,
|
||||
Emitter<ShiftDetailsState> emit,
|
||||
) {
|
||||
emit(state.copyWith(shiftViewModel: state.shiftViewModel.copyWith()));
|
||||
}
|
||||
|
||||
void _onConfirm(
|
||||
ShiftConfirmEvent event,
|
||||
Emitter<ShiftDetailsState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
await getIt<ShiftsRepository>().confirmShift(state.shiftViewModel);
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
shiftViewModel: state.shiftViewModel.copyWith(
|
||||
status: EventShiftRoleStaffStatus.confirmed,
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
if (e is DisplayableException) {
|
||||
emit(state.copyWith(errorMessage: e.message));
|
||||
}else{
|
||||
emit(state.copyWith(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
emit(state.copyWith(isLoading: false));
|
||||
|
||||
}
|
||||
|
||||
void _onClockIn(
|
||||
ShiftClockInEvent event, Emitter<ShiftDetailsState> emit) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
|
||||
try {
|
||||
await getIt<ShiftsRepository>().clockInShift(state.shiftViewModel);
|
||||
_geofencingStream?.cancel();
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoading: false,
|
||||
shiftViewModel: state.shiftViewModel.copyWith(
|
||||
status: EventShiftRoleStaffStatus.ongoing,
|
||||
clockIn: DateTime.now(),
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
if (e is DisplayableException) {
|
||||
emit(state.copyWith(errorMessage: e.message));
|
||||
}else{
|
||||
emit(state.copyWith(errorMessage: e.toString()));
|
||||
}
|
||||
emit(state.copyWith(isLoading: false));
|
||||
}
|
||||
_startOngoingTimer();
|
||||
|
||||
getIt<ForceClockoutService>().startTrackOngoingLocation(
|
||||
state.shiftViewModel,
|
||||
() {
|
||||
onForceUpdateUI();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _onComplete(
|
||||
ShiftCompleteEvent event,
|
||||
Emitter<ShiftDetailsState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
try {
|
||||
emit(
|
||||
state.copyWith(
|
||||
shiftViewModel: state.shiftViewModel.copyWith(
|
||||
status: EventShiftRoleStaffStatus.completed,
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
if (e is DisplayableException) {
|
||||
emit(state.copyWith(errorMessage: e.message));
|
||||
}else{
|
||||
emit(state.copyWith(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
emit(state.copyWith(isLoading: false));
|
||||
|
||||
}
|
||||
|
||||
void _onDecline(
|
||||
ShiftDeclineEvent event,
|
||||
Emitter<ShiftDetailsState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
try {
|
||||
await getIt<ShiftsRepository>().declineShift(
|
||||
state.shiftViewModel, event.reason, event.additionalReason);
|
||||
emit(state.copyWith(
|
||||
needPop: true,
|
||||
shiftViewModel: state.shiftViewModel.copyWith(
|
||||
status: EventShiftRoleStaffStatus.canceledByStaff,
|
||||
)));
|
||||
} catch (e) {
|
||||
if (e is DisplayableException) {
|
||||
emit(state.copyWith(errorMessage: e.message));
|
||||
}else{
|
||||
emit(state.copyWith(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
emit(state.copyWith(isLoading: false));
|
||||
|
||||
}
|
||||
|
||||
void _onErrorWasShown(
|
||||
ShiftErrorWasShownEvent event,
|
||||
Emitter<ShiftDetailsState> emit,
|
||||
) {
|
||||
emit(state.copyWith(errorMessage: ''));
|
||||
}
|
||||
|
||||
void _onCancel(
|
||||
ShiftCancelEvent event,
|
||||
Emitter<ShiftDetailsState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
try {
|
||||
await getIt<ShiftsRepository>().cancelShift(
|
||||
state.shiftViewModel, event.reason, event.additionalReason);
|
||||
emit(state.copyWith(
|
||||
shiftViewModel: state.shiftViewModel.copyWith(
|
||||
status: EventShiftRoleStaffStatus.canceledByStaff,
|
||||
)));
|
||||
} catch (e) {
|
||||
print('!!!!!! ${e}');
|
||||
if (e is DisplayableException) {
|
||||
emit(state.copyWith(errorMessage: e.message));
|
||||
}else{
|
||||
emit(state.copyWith(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
emit(state.copyWith(isLoading: false));
|
||||
|
||||
}
|
||||
|
||||
void _onRefresh(
|
||||
ShiftRefreshEvent event,
|
||||
Emitter<ShiftDetailsState> emit,
|
||||
) {
|
||||
emit(state.copyWith(
|
||||
shiftViewModel: event.shift,
|
||||
proximityState: GeofencingProximityState.none));
|
||||
}
|
||||
|
||||
void _onCheckGeocoding(
|
||||
ShiftCheckGeocodingEvent event,
|
||||
Emitter<ShiftDetailsState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(
|
||||
proximityState: GeofencingProximityState.none,
|
||||
));
|
||||
|
||||
await _checkByGeocoding(emit);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_timer?.cancel();
|
||||
_refreshTimer?.cancel();
|
||||
_geofencingStream?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _runRefreshTimer() {
|
||||
_refreshTimer = Timer.periodic(
|
||||
const Duration(seconds: 10),
|
||||
(timer) async {
|
||||
final shift = await getIt<ShiftsRepository>()
|
||||
.getShiftById(state.shiftViewModel.id);
|
||||
if (shift == null) {
|
||||
return;
|
||||
}
|
||||
add((ShiftRefreshEvent(shift)));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _checkByGeocoding(Emitter<ShiftDetailsState> emit) async {
|
||||
if (![
|
||||
EventShiftRoleStaffStatus.assigned,
|
||||
EventShiftRoleStaffStatus.confirmed,
|
||||
EventShiftRoleStaffStatus.ongoing
|
||||
].contains(state.shiftViewModel.status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final geolocationCheck =
|
||||
await _geofencingService.requestGeolocationPermission();
|
||||
emit(
|
||||
state.copyWith(
|
||||
proximityState: switch (geolocationCheck) {
|
||||
GeolocationStatus.disabled =>
|
||||
GeofencingProximityState.locationDisabled,
|
||||
GeolocationStatus.denied => GeofencingProximityState.permissionDenied,
|
||||
GeolocationStatus.prohibited => GeofencingProximityState.goToSettings,
|
||||
GeolocationStatus.onlyInUse => GeofencingProximityState.onlyInUse,
|
||||
GeolocationStatus.enabled => null,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if (geolocationCheck == GeolocationStatus.enabled) {
|
||||
_geofencingStream = _geofencingService
|
||||
.isInRangeStream(
|
||||
pointLatitude: state.shiftViewModel.locationLat,
|
||||
pointLongitude: state.shiftViewModel.locationLon,
|
||||
)
|
||||
.listen(
|
||||
(isInRange) {
|
||||
|
||||
add(StaffGeofencingUpdate(isInRange: isInRange));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _startOngoingTimer() {
|
||||
_timer?.cancel();
|
||||
_timer = Timer.periodic(
|
||||
const Duration(seconds: 10),
|
||||
(timer) {
|
||||
if (state.shiftViewModel.status != EventShiftRoleStaffStatus.ongoing) {
|
||||
timer.cancel();
|
||||
} else {
|
||||
add(const ShiftUpdateTimerEvent());
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> onForceUpdateUI() async {
|
||||
_timer?.cancel();
|
||||
if (!isClosed) {
|
||||
final shift =
|
||||
await getIt<ShiftsRepository>().getShiftById(state.shiftViewModel.id);
|
||||
if (shift == null) {
|
||||
return;
|
||||
}
|
||||
add((ShiftRefreshEvent(shift)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
part of 'shift_details_bloc.dart';
|
||||
|
||||
@immutable
|
||||
sealed class ShiftDetailsEvent {
|
||||
const ShiftDetailsEvent();
|
||||
}
|
||||
|
||||
class ShiftDetailsInitialEvent extends ShiftDetailsEvent {
|
||||
final ShiftEntity shift;
|
||||
|
||||
const ShiftDetailsInitialEvent({required this.shift});
|
||||
}
|
||||
|
||||
class StaffGeofencingUpdate extends ShiftDetailsEvent {
|
||||
const StaffGeofencingUpdate({required this.isInRange});
|
||||
|
||||
final bool isInRange;
|
||||
}
|
||||
|
||||
class ShiftUpdateTimerEvent extends ShiftDetailsEvent {
|
||||
const ShiftUpdateTimerEvent();
|
||||
}
|
||||
|
||||
class ShiftCompleteEvent extends ShiftDetailsEvent {
|
||||
const ShiftCompleteEvent();
|
||||
}
|
||||
|
||||
class ShiftClockInEvent extends ShiftDetailsEvent {
|
||||
const ShiftClockInEvent();
|
||||
}
|
||||
|
||||
class ShiftConfirmEvent extends ShiftDetailsEvent {
|
||||
const ShiftConfirmEvent();
|
||||
}
|
||||
|
||||
class ShiftDeclineEvent extends ShiftDetailsEvent {
|
||||
final String? reason;
|
||||
final String? additionalReason;
|
||||
|
||||
const ShiftDeclineEvent(this.reason, this.additionalReason);
|
||||
}
|
||||
|
||||
class ShiftCancelEvent extends ShiftDetailsEvent {
|
||||
final String? reason;
|
||||
final String? additionalReason;
|
||||
|
||||
const ShiftCancelEvent(this.reason, this.additionalReason);
|
||||
}
|
||||
|
||||
class ShiftRefreshEvent extends ShiftDetailsEvent {
|
||||
final ShiftEntity shift;
|
||||
|
||||
const ShiftRefreshEvent(this.shift);
|
||||
}
|
||||
|
||||
class ShiftCheckGeocodingEvent extends ShiftDetailsEvent {
|
||||
const ShiftCheckGeocodingEvent();
|
||||
}
|
||||
|
||||
class ShiftErrorWasShownEvent extends ShiftDetailsEvent {
|
||||
const ShiftErrorWasShownEvent();
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
part of 'shift_details_bloc.dart';
|
||||
|
||||
@immutable
|
||||
class ShiftDetailsState {
|
||||
final ShiftEntity shiftViewModel;
|
||||
final bool isToFar;
|
||||
final bool isLoading;
|
||||
final GeofencingProximityState proximityState;
|
||||
final String? errorMessage;
|
||||
final bool needPop;
|
||||
|
||||
const ShiftDetailsState({
|
||||
required this.shiftViewModel,
|
||||
this.isToFar = true,
|
||||
this.isLoading = false,
|
||||
this.proximityState = GeofencingProximityState.none,
|
||||
this.errorMessage,
|
||||
this.needPop = false,
|
||||
});
|
||||
|
||||
ShiftDetailsState copyWith({
|
||||
ShiftEntity? shiftViewModel,
|
||||
bool? isToFar,
|
||||
bool? isLoading,
|
||||
GeofencingProximityState? proximityState,
|
||||
String? errorMessage,
|
||||
bool? needPop
|
||||
}) {
|
||||
return ShiftDetailsState(
|
||||
shiftViewModel: shiftViewModel ?? this.shiftViewModel,
|
||||
isToFar: isToFar ?? this.isToFar,
|
||||
isLoading: isLoading ?? false,
|
||||
proximityState: proximityState ?? this.proximityState,
|
||||
errorMessage: errorMessage,
|
||||
needPop: needPop ?? this.needPop,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum GeofencingProximityState {
|
||||
none,
|
||||
tooFar,
|
||||
locationDisabled,
|
||||
goToSettings,
|
||||
onlyInUse,
|
||||
permissionDenied,
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow/core/application/di/injectable.dart';
|
||||
import 'package:krow/features/shifts/domain/blocs/shifts_list_bloc/shifts_event.dart';
|
||||
import 'package:krow/features/shifts/domain/blocs/shifts_list_bloc/shifts_state.dart';
|
||||
import 'package:krow/features/shifts/domain/services/force_clockout_service.dart';
|
||||
import 'package:krow/features/shifts/domain/shift_entity.dart';
|
||||
import 'package:krow/features/shifts/domain/shifts_repository.dart';
|
||||
|
||||
class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
||||
var indexToStatus = <int, ShiftStatusFilterType>{
|
||||
0: ShiftStatusFilterType.assigned,
|
||||
1: ShiftStatusFilterType.confirmed,
|
||||
2: ShiftStatusFilterType.ongoing,
|
||||
3: ShiftStatusFilterType.completed,
|
||||
4: ShiftStatusFilterType.canceled,
|
||||
};
|
||||
|
||||
ShiftsBloc()
|
||||
: super(const ShiftsState(tabs: {
|
||||
0: ShiftTabState(items: [], isLoading: true),
|
||||
1: ShiftTabState(items: []),
|
||||
2: ShiftTabState(items: []),
|
||||
3: ShiftTabState(items: []),
|
||||
4: ShiftTabState(items: []),
|
||||
})) {
|
||||
on<ShiftsInitialEvent>(_onInitial);
|
||||
on<ShiftsTabChangedEvent>(_onTabChanged);
|
||||
on<LoadTabShiftEvent>(_onLoadTabItems);
|
||||
on<LoadMoreShiftEvent>(_onLoadMoreTabItems);
|
||||
on<ReloadMissingBreakShift>(_onReloadMissingBreakShift);
|
||||
|
||||
getIt<ShiftsRepository>().statusStream.listen((event) {
|
||||
add(LoadTabShiftEvent(status: event.index));
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _onInitial(ShiftsInitialEvent event, emit) async {
|
||||
add(const LoadTabShiftEvent(status: 0));
|
||||
add(const LoadTabShiftEvent(status: 2));
|
||||
|
||||
var missedShifts =
|
||||
await getIt<ShiftsRepository>().getMissBreakFinishedShift();
|
||||
if (missedShifts.isNotEmpty) {
|
||||
emit(state.copyWith(
|
||||
missedShifts: missedShifts,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onTabChanged(ShiftsTabChangedEvent event, emit) async {
|
||||
emit(state.copyWith(tabIndex: event.tabIndex));
|
||||
final currentTabState = state.tabs[event.tabIndex]!;
|
||||
if (currentTabState.items.isEmpty && !currentTabState.isLoading) {
|
||||
add(LoadTabShiftEvent(status: event.tabIndex));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onLoadTabItems(LoadTabShiftEvent event, emit) async {
|
||||
await _fetchShifts(event.status, null, emit);
|
||||
}
|
||||
|
||||
Future<void> _onLoadMoreTabItems(LoadMoreShiftEvent event, emit) async {
|
||||
final currentTabState = state.tabs[event.status]!;
|
||||
if (!currentTabState.hasMoreItems || currentTabState.isLoading) return;
|
||||
await _fetchShifts(event.status, currentTabState.items, emit);
|
||||
}
|
||||
|
||||
_fetchShifts(int tabIndex, List<ShiftEntity>? previousItems, emit) async {
|
||||
if (previousItems != null && previousItems.lastOrNull?.cursor == null) {
|
||||
return;
|
||||
}
|
||||
final currentTabState = state.tabs[tabIndex]!;
|
||||
|
||||
emit(state.copyWith(
|
||||
tabs: {
|
||||
...state.tabs,
|
||||
tabIndex: currentTabState.copyWith(isLoading: true),
|
||||
},
|
||||
));
|
||||
|
||||
try {
|
||||
var items = await getIt<ShiftsRepository>().getShifts(
|
||||
statusFilter: indexToStatus[tabIndex]!,
|
||||
lastItemId: previousItems?.lastOrNull?.cursor,
|
||||
);
|
||||
|
||||
// if(items.isNotEmpty){
|
||||
// items = List.generate(20, (i)=>items[0]);
|
||||
// }
|
||||
var allItems = (previousItems ?? [])..addAll(items);
|
||||
emit(state.copyWith(
|
||||
tabs: {
|
||||
...state.tabs,
|
||||
tabIndex: currentTabState.copyWith(
|
||||
items: allItems,
|
||||
hasMoreItems: items.isNotEmpty,
|
||||
isLoading: false,
|
||||
),
|
||||
},
|
||||
));
|
||||
|
||||
if (tabIndex == 2 &&
|
||||
allItems.isNotEmpty ) {
|
||||
getIt<ForceClockoutService>()
|
||||
.startTrackOngoingLocation(allItems.first, () {});
|
||||
}
|
||||
} catch (e, s) {
|
||||
debugPrint(e.toString());
|
||||
debugPrint(s.toString());
|
||||
emit(state.copyWith(
|
||||
tabs: {
|
||||
...state.tabs,
|
||||
tabIndex: currentTabState.copyWith(isLoading: false),
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onReloadMissingBreakShift(
|
||||
ReloadMissingBreakShift event, emit) async {
|
||||
emit(state.copyWith(missedShifts: []));
|
||||
var missedShifts =
|
||||
await getIt<ShiftsRepository>().getMissBreakFinishedShift();
|
||||
emit(state.copyWith(missedShifts: missedShifts));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
getIt<ShiftsRepository>().dispose();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
sealed class ShiftsEvent {
|
||||
const ShiftsEvent();
|
||||
}
|
||||
|
||||
class ShiftsInitialEvent extends ShiftsEvent {
|
||||
const ShiftsInitialEvent();
|
||||
}
|
||||
|
||||
class ShiftsTabChangedEvent extends ShiftsEvent {
|
||||
final int tabIndex;
|
||||
|
||||
const ShiftsTabChangedEvent({required this.tabIndex});
|
||||
}
|
||||
|
||||
class LoadTabShiftEvent extends ShiftsEvent {
|
||||
final int status;
|
||||
|
||||
const LoadTabShiftEvent({required this.status});
|
||||
}
|
||||
|
||||
class LoadMoreShiftEvent extends ShiftsEvent {
|
||||
final int status;
|
||||
|
||||
const LoadMoreShiftEvent({required this.status});
|
||||
}
|
||||
|
||||
class ReloadMissingBreakShift extends ShiftsEvent {
|
||||
|
||||
const ReloadMissingBreakShift();
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import 'package:krow/features/shifts/domain/shift_entity.dart';
|
||||
|
||||
class ShiftsState {
|
||||
final bool isLoading;
|
||||
final int tabIndex;
|
||||
|
||||
final Map<int, ShiftTabState> tabs;
|
||||
final List<ShiftEntity> missedShifts;
|
||||
|
||||
const ShiftsState(
|
||||
{this.isLoading = false,
|
||||
this.tabIndex = 0,
|
||||
required this.tabs,
|
||||
this.missedShifts = const []});
|
||||
|
||||
ShiftsState copyWith({
|
||||
bool? isLoading,
|
||||
int? tabIndex,
|
||||
Map<int, ShiftTabState>? tabs,
|
||||
List<ShiftEntity>? missedShifts,
|
||||
}) {
|
||||
return ShiftsState(
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
tabIndex: tabIndex ?? this.tabIndex,
|
||||
tabs: tabs ?? this.tabs,
|
||||
missedShifts: missedShifts ?? this.missedShifts,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ShiftTabState {
|
||||
final List<ShiftEntity> items;
|
||||
final bool isLoading;
|
||||
final bool hasMoreItems;
|
||||
|
||||
const ShiftTabState({
|
||||
required this.items,
|
||||
this.isLoading = false,
|
||||
this.hasMoreItems = true,
|
||||
});
|
||||
|
||||
ShiftTabState copyWith({
|
||||
List<ShiftEntity>? items,
|
||||
bool? isLoading,
|
||||
bool? hasMoreItems,
|
||||
}) {
|
||||
return ShiftTabState(
|
||||
items: items ?? this.items,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
hasMoreItems: hasMoreItems ?? this.hasMoreItems,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user