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,201 @@
import 'dart:developer';
import 'package:easy_localization/easy_localization.dart';
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/enums/pagination_status.dart';
import 'package:krow/core/data/enums/state_status.dart';
import 'package:krow/features/earning/domain/entities/earning_shift_entity.dart';
import 'package:krow/features/earning/domain/entities/earnings_summary_entity.dart';
import 'package:krow/features/earning/domain/staff_earning_repository.dart';
part 'earnings_data.dart';
part 'earnings_event.dart';
part 'earnings_state.dart';
class EarningsBloc extends Bloc<EarningsEvent, EarningsState> {
EarningsBloc()
: super(const EarningsState.initial()
..copyWith(
tabs: defaultEarningTabs,
)) {
on<EarningsInitEvent>(_onInit);
on<EarningsSwitchBalancePeriodEvent>(_onSwitchBalancePeriod);
on<EarningsTabChangedEvent>(_onTabChanged);
on<LoadAdditionalEarnings>(_onLoadAdditionalEarnings);
on<ReloadCurrentEarnings>(_onReloadCurrentEarnings);
on<ConfirmStaffEarning>(_onConfirmStaffEarning);
on<DisputeStaffEarning>(_onDisputeStaffEarning);
}
final _earningRepository = getIt<StaffEarningRepository>();
Future<void> _onInit(
EarningsInitEvent event,
Emitter<EarningsState> emit,
) async {
add(const LoadAdditionalEarnings());
emit(state.copyWith(status: StateStatus.loading));
EarningsSummaryEntity? earningsData;
try {
earningsData = await _earningRepository.getStaffEarningsData();
} catch (except) {
log('Error in EarningsBloc, on EarningsInitEvent', error: except);
}
emit(state.copyWith(earnings: earningsData, status: StateStatus.idle));
}
void _onSwitchBalancePeriod(
EarningsSwitchBalancePeriodEvent event,
Emitter<EarningsState> emit,
) {
emit(state.copyWith(balancePeriod: event.period));
}
void _onTabChanged(
EarningsTabChangedEvent event,
Emitter<EarningsState> emit,
) {
emit(state.copyWith(tabIndex: event.tabIndex));
if (state.currentTab.status == PaginationStatus.initial) {
add(const LoadAdditionalEarnings());
}
}
Future<void> _onLoadAdditionalEarnings(
LoadAdditionalEarnings event,
Emitter<EarningsState> emit,
) async {
if (!state.currentTab.status.allowLoad) return;
emit(state.updateCurrentTab(status: PaginationStatus.loading));
final tabIndex = state.tabIndex;
try {
final earningsBatch = await _earningRepository.getEarningsBatch(
status: state.currentTab.loadingKey,
lastEntryCursor: state.currentTab.paginationCursor,
);
emit(
state.copyWithTab(
tabIndex: tabIndex,
tab: state.tabs[tabIndex].copyWith(
status: earningsBatch.hasNextBatch
? PaginationStatus.idle
: PaginationStatus.end,
items: [...state.tabs[tabIndex].items, ...earningsBatch.earnings],
hasMoreItems: earningsBatch.hasNextBatch,
),
),
);
} catch (except) {
log(
'Error in EarningsBloc, on LoadAdditionalEarnings',
error: except,
);
emit(
state.copyWithTab(
tabIndex: tabIndex,
tab: state.tabs[tabIndex].copyWith(
status: PaginationStatus.error,
),
),
);
}
if (state.tabs[tabIndex].status == PaginationStatus.loading) {
emit(
state.copyWithTab(
tabIndex: tabIndex,
tab: state.tabs[tabIndex].copyWith(
status: PaginationStatus.idle,
),
),
);
}
}
void _onReloadCurrentEarnings(
ReloadCurrentEarnings event,
Emitter<EarningsState> emit,
) {
emit(
state.copyWithTab(
tab: EarningsTabState.initial(
label: state.currentTab.label,
loadingKey: state.currentTab.loadingKey,
),
),
);
add(const LoadAdditionalEarnings());
}
Future<void> _onEarningAction(
Future<EarningShiftEntity?> earningFuture,
String eventName,
Emitter<EarningsState> emit,
) async {
emit(state.copyWith(status: StateStatus.loading));
EarningShiftEntity? earning;
try {
earning = await earningFuture;
} catch (except) {
log(
'Error in EarningsBloc, on $eventName',
error: except,
);
}
var tabData = state.currentTab;
if (earning != null) {
final earningIndex =
state.currentTab.items.indexWhere((item) => item.id == earning?.id);
if (earningIndex >= 0) {
tabData = state.currentTab.copyWith(
items: List.from(state.currentTab.items)..[earningIndex] = earning,
);
}
}
emit(
state.copyWithTab(
status: StateStatus.idle,
tab: tabData,
),
);
}
Future<void> _onConfirmStaffEarning(
ConfirmStaffEarning event,
Emitter<EarningsState> emit,
) {
return _onEarningAction(
_earningRepository.confirmStaffEarning(
earningId: event.earningId,
),
'ConfirmStaffEarning',
emit,
);
}
Future<void> _onDisputeStaffEarning(
DisputeStaffEarning event,
Emitter<EarningsState> emit,
) async {
return _onEarningAction(
_earningRepository.disputeStaffEarning(
id: event.earningId,
reason: event.reason,
details: event.details,
),
'DeclineStaffEarning',
emit,
);
}
}

View File

@@ -0,0 +1,85 @@
part of 'earnings_bloc.dart';
enum BalancePeriod {
week,
month,
}
class EarningsTabState {
const EarningsTabState({
required this.label,
required this.loadingKey,
required this.items,
this.status = PaginationStatus.idle,
this.hasMoreItems = true,
this.paginationCursor,
});
const EarningsTabState.initial({
required this.label,
required this.loadingKey,
this.items = const [],
this.status = PaginationStatus.initial,
this.hasMoreItems = true,
this.paginationCursor,
});
final String label;
final String loadingKey;
final List<EarningShiftEntity> items;
final PaginationStatus status;
final bool hasMoreItems;
final String? paginationCursor;
EarningsTabState copyWith({
List<EarningShiftEntity>? items,
PaginationStatus? status,
bool? hasMoreItems,
String? label,
String? loadingKey,
String? paginationCursor,
}) {
return EarningsTabState(
loadingKey: loadingKey ?? this.loadingKey,
items: items ?? this.items,
status: status ?? this.status,
hasMoreItems: hasMoreItems ?? this.hasMoreItems,
label: label ?? this.label,
paginationCursor: paginationCursor ?? this.paginationCursor,
);
}
}
const defaultEarningTabs = [
EarningsTabState.initial(
label: 'new_earning',
loadingKey: 'new' // Confirmed by admin
),
EarningsTabState.initial(
label: 'confirmed',
loadingKey: 'confirmed', // Confirmed by staff
),
EarningsTabState.initial(
label: 'disputed',
loadingKey: 'disputed', // Declined by staff
),
EarningsTabState.initial(
label: 'sent',
loadingKey: 'sent', // Should remain sent
),
EarningsTabState.initial(
label: 'received',
loadingKey: 'paid', // Should remain paid
),
];
// $of = match ($status) {
// 'new' => fn(Builder $q) => $q->whereIn('status', [StaffPaymentStatus::new]),
// 'confirmed' => fn(Builder $q) => $q->whereIn('status', [StaffPaymentStatus::confirmed_by_admin]),
// 'pending' => fn(Builder $q) => $q->whereIn('status', [
// StaffPaymentStatus::decline_by_staff,
// StaffPaymentStatus::confirmed_by_staff,
// ]),
// 'sent' => fn(Builder $q) => $q->whereIn('status', [StaffPaymentStatus::sent]),
// 'paid' => fn(Builder $q) => $q->whereIn('status', [StaffPaymentStatus::paid]),
// };

View File

@@ -0,0 +1,48 @@
part of 'earnings_bloc.dart';
@immutable
sealed class EarningsEvent {
const EarningsEvent();
}
class EarningsInitEvent extends EarningsEvent {
const EarningsInitEvent();
}
class EarningsSwitchBalancePeriodEvent extends EarningsEvent {
final BalancePeriod period;
const EarningsSwitchBalancePeriodEvent({required this.period});
}
class EarningsTabChangedEvent extends EarningsEvent {
final int tabIndex;
const EarningsTabChangedEvent({required this.tabIndex});
}
class LoadAdditionalEarnings extends EarningsEvent {
const LoadAdditionalEarnings();
}
class ReloadCurrentEarnings extends EarningsEvent {
const ReloadCurrentEarnings();
}
class ConfirmStaffEarning extends EarningsEvent {
const ConfirmStaffEarning(this.earningId);
final String earningId;
}
class DisputeStaffEarning extends EarningsEvent {
const DisputeStaffEarning({
required this.earningId,
required this.reason,
required this.details,
});
final String earningId;
final String reason;
final String details;
}

View File

@@ -0,0 +1,92 @@
part of 'earnings_bloc.dart';
@immutable
class EarningsState {
const EarningsState({
required this.status,
required this.tabIndex,
required this.balancePeriod,
required this.earnings,
required this.tabs,
});
const EarningsState.initial({
this.status = StateStatus.idle,
this.tabIndex = 0,
this.balancePeriod = BalancePeriod.week,
this.earnings = const EarningsSummaryEntity.empty(),
this.tabs = defaultEarningTabs,
});
final StateStatus status;
final int tabIndex;
final BalancePeriod balancePeriod;
final EarningsSummaryEntity earnings;
final List<EarningsTabState> tabs;
EarningsTabState get currentTab => tabs[tabIndex];
double get totalEarnings {
return balancePeriod == BalancePeriod.week
? earnings.totalEarningsByWeek
: earnings.totalEarningsByMonth;
}
double get totalHours {
return balancePeriod == BalancePeriod.week
? earnings.totalWorkedHoursByWeek
: earnings.totalWorkedHoursByMonth;
}
double get totalPayout {
return balancePeriod == BalancePeriod.week
? earnings.payoutByWeek
: earnings.payoutByMonth;
}
EarningsState copyWith({
StateStatus? status,
int? tabIndex,
BalancePeriod? balancePeriod,
EarningsSummaryEntity? earnings,
List<EarningsTabState>? tabs,
}) {
return EarningsState(
status: status ?? this.status,
tabIndex: tabIndex ?? this.tabIndex,
balancePeriod: balancePeriod ?? this.balancePeriod,
earnings: earnings ?? this.earnings,
tabs: tabs ?? this.tabs,
);
}
EarningsState copyWithTab({
required EarningsTabState tab,
int? tabIndex,
StateStatus? status,
}) {
return copyWith(
tabs: List.from(tabs)..[tabIndex ?? this.tabIndex] = tab,
status: status,
);
}
EarningsState updateCurrentTab({
List<EarningShiftEntity>? items,
PaginationStatus? status,
bool? hasMoreItems,
String? label,
String? paginationCursor,
}) {
return copyWith(
tabs: List.from(tabs)
..[tabIndex] = currentTab.copyWith(
items: items,
status: status,
hasMoreItems: hasMoreItems,
label: label,
paginationCursor: paginationCursor,
),
);
}
}