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,70 @@
import 'package:bloc/bloc.dart';
import 'package:krow/core/application/clients/api/api_exception.dart';
import 'package:krow/core/application/di/injectable.dart';
import 'package:krow/features/invoice/data/models/invoice_decline_model.dart';
import 'package:krow/features/invoice/data/models/invoice_model.dart';
import 'package:krow/features/invoice/domain/invoices_repository.dart';
import 'package:meta/meta.dart';
part 'invoice_details_event.dart';
part 'invoice_details_state.dart';
class InvoiceDetailsBloc
extends Bloc<InvoiceDetailsEvent, InvoiceDetailsState> {
InvoiceDetailsBloc(InvoiceModel invoice)
: super(InvoiceDetailsState(invoiceModel: invoice)) {
on<InvoiceApproveEvent>(_onInvoiceApproveEvent);
on<InvoiceDisputeEvent>(_onInvoiceDisputeEvent);
}
void _onInvoiceApproveEvent(
InvoiceApproveEvent event, Emitter<InvoiceDetailsState> emit) async {
emit(state.copyWith(inLoading: true));
try {
await getIt<InvoicesRepository>().approveInvoice(
invoiceId: state.invoiceModel?.id ?? '',
);
emit(state.copyWith(inLoading: false, success: true));
return;
} catch (e) {
if (e is DisplayableException) {
emit(state.copyWith(inLoading: false, showErrorPopup: e.message));
return;
}
}
emit(state.copyWith(inLoading: false));
}
void _onInvoiceDisputeEvent(
InvoiceDisputeEvent event, Emitter<InvoiceDetailsState> emit) async {
emit(state.copyWith(inLoading: true));
try {
await getIt<InvoicesRepository>().disputeInvoice(
invoiceId: state.invoiceModel?.id ?? '',
reason: event.reason,
comment: event.comment,
);
emit(
state.copyWith(
inLoading: false,
invoiceModel: state.invoiceModel?.copyWith(
status: InvoiceStatus.disputed,
dispute: InvoiceDeclineModel(
id: '1',
reason: event.reason,
details: event.comment,
),
),
),
);
return;
} catch (e) {
print(e);
if (e is DisplayableException) {
emit(state.copyWith(inLoading: false, showErrorPopup: e.message));
return;
}
}
emit(state.copyWith(inLoading: false));
}
}

View File

@@ -0,0 +1,18 @@
part of 'invoice_details_bloc.dart';
@immutable
sealed class InvoiceDetailsEvent {}
class InvoiceApproveEvent extends InvoiceDetailsEvent {
InvoiceApproveEvent();
}
class InvoiceDisputeEvent extends InvoiceDetailsEvent {
final String reason;
final String comment;
InvoiceDisputeEvent({
required this.reason,
required this.comment,
});
}

View File

@@ -0,0 +1,20 @@
part of 'invoice_details_bloc.dart';
@immutable
class InvoiceDetailsState {
final String? showErrorPopup;
final bool inLoading;
final bool success;
final InvoiceModel? invoiceModel;
const InvoiceDetailsState( {this.showErrorPopup, this.inLoading = false,this.invoiceModel, this.success = false});
InvoiceDetailsState copyWith({String? showErrorPopup, bool? inLoading, InvoiceModel? invoiceModel, bool? success}) {
return InvoiceDetailsState(
showErrorPopup: showErrorPopup ?? this.showErrorPopup,
inLoading: inLoading ?? false,
success: success ?? this.success,
invoiceModel: invoiceModel ?? this.invoiceModel,
);
}
}

View File

@@ -0,0 +1,113 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow/core/application/di/injectable.dart';
import '../../invoice_entity.dart';
import '../../invoices_repository.dart';
import 'invoice_event.dart';
import 'invoice_state.dart';
class InvoiceBloc extends Bloc<InvoiceEvent, InvoicesState> {
var indexToStatus = <int, InvoiceStatusFilterType>{
0: InvoiceStatusFilterType.all,
1: InvoiceStatusFilterType.open,
2: InvoiceStatusFilterType.disputed,
3: InvoiceStatusFilterType.resolved,
4: InvoiceStatusFilterType.paid,
5: InvoiceStatusFilterType.overdue,
6: InvoiceStatusFilterType.verified,
};
InvoiceBloc()
: super(const InvoicesState(tabs: {
0: InvoiceTabState(items: [], inLoading: true),
1: InvoiceTabState(items: []),
2: InvoiceTabState(items: []),
3: InvoiceTabState(items: []),
4: InvoiceTabState(items: []),
5: InvoiceTabState(items: []),
6: InvoiceTabState(items: []),
})) {
on<InvoiceInitialEvent>(_onInitial);
on<InvoiceTabChangedEvent>(_onTabChanged);
on<LoadTabInvoiceEvent>(_onLoadTabItems);
on<LoadMoreInvoiceEvent>(_onLoadMoreTabItems);
getIt<InvoicesRepository>().statusStream.listen((event) {
add(LoadTabInvoiceEvent(status: event.index));
});
}
Future<void> _onInitial(InvoiceInitialEvent event, emit) async {
add(const LoadTabInvoiceEvent(status: 0));
}
Future<void> _onTabChanged(InvoiceTabChangedEvent event, emit) async {
emit(state.copyWith(tabIndex: event.tabIndex));
final currentTabState = state.tabs[event.tabIndex]!;
if (currentTabState.items.isEmpty && !currentTabState.inLoading) {
add(LoadTabInvoiceEvent(status: event.tabIndex));
}
}
Future<void> _onLoadTabItems(LoadTabInvoiceEvent event, emit) async {
await _fetchInvoices(event.status, null, emit);
}
Future<void> _onLoadMoreTabItems(LoadMoreInvoiceEvent event, emit) async {
final currentTabState = state.tabs[event.status]!;
if (!currentTabState.hasMoreItems || currentTabState.inLoading) return;
await _fetchInvoices(event.status, currentTabState.items, emit);
}
_fetchInvoices(
int tabIndex, List<InvoiceListEntity>? 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(inLoading: true),
},
));
try {
var items = await getIt<InvoicesRepository>().getInvoices(
statusFilter: indexToStatus[tabIndex]!,
lastItemId: previousItems?.lastOrNull?.cursor,
);
// if(items.isNotEmpty){
// items = List.generate(20, (i)=>items[0]);
// }
emit(state.copyWith(
tabs: {
...state.tabs,
tabIndex: currentTabState.copyWith(
items: (previousItems ?? [])..addAll(items),
hasMoreItems: items.isNotEmpty,
inLoading: false,
),
},
));
} catch (e, s) {
debugPrint(e.toString());
debugPrint(s.toString());
emit(state.copyWith(
tabs: {
...state.tabs,
tabIndex: currentTabState.copyWith(inLoading: false),
},
));
}
}
@override
Future<void> close() {
getIt<InvoicesRepository>().dispose();
return super.close();
}
}

View File

@@ -0,0 +1,25 @@
sealed class InvoiceEvent {
const InvoiceEvent();
}
class InvoiceInitialEvent extends InvoiceEvent {
const InvoiceInitialEvent();
}
class InvoiceTabChangedEvent extends InvoiceEvent {
final int tabIndex;
const InvoiceTabChangedEvent({required this.tabIndex});
}
class LoadTabInvoiceEvent extends InvoiceEvent {
final int status;
const LoadTabInvoiceEvent({required this.status});
}
class LoadMoreInvoiceEvent extends InvoiceEvent {
final int status;
const LoadMoreInvoiceEvent({required this.status});
}

View File

@@ -0,0 +1,47 @@
import 'package:krow/features/invoice/domain/invoice_entity.dart';
class InvoicesState {
final bool inLoading;
final int tabIndex;
final Map<int, InvoiceTabState> tabs;
const InvoicesState(
{this.inLoading = false, this.tabIndex = 0, required this.tabs});
InvoicesState copyWith({
bool? inLoading,
int? tabIndex,
Map<int, InvoiceTabState>? tabs,
}) {
return InvoicesState(
inLoading: inLoading ?? this.inLoading,
tabIndex: tabIndex ?? this.tabIndex,
tabs: tabs ?? this.tabs,
);
}
}
class InvoiceTabState {
final List<InvoiceListEntity> items;
final bool inLoading;
final bool hasMoreItems;
const InvoiceTabState({
required this.items,
this.inLoading = false,
this.hasMoreItems = true,
});
InvoiceTabState copyWith({
List<InvoiceListEntity>? items,
bool? inLoading,
bool? hasMoreItems,
}) {
return InvoiceTabState(
items: items ?? this.items,
inLoading: inLoading ?? this.inLoading,
hasMoreItems: hasMoreItems ?? this.hasMoreItems,
);
}
}

View File

@@ -0,0 +1,79 @@
import 'package:krow/features/invoice/data/models/invoice_model.dart';
class InvoiceListEntity {
final String id;
final InvoiceStatus status;
String? cursor;
final String invoiceNumber;
final String eventName;
final int count;
final double value;
final DateTime date;
final DateTime dueAt;
final String? poNumber;
final InvoiceModel? invoiceModel;
InvoiceListEntity({
required this.id,
required this.status,
required this.invoiceNumber,
required this.eventName,
required this.count,
required this.value,
required this.date,
required this.dueAt,
this.poNumber,
this.cursor,
this.invoiceModel,
});
InvoiceListEntity copyWith({
String? id,
String? cursor,
InvoiceStatus? status,
String? invoiceNumber,
String? eventName,
int? count,
double? value,
DateTime? date,
DateTime? dueAt,
String? poNumber,
}) {
return InvoiceListEntity(
id: id ?? this.id,
cursor: cursor ?? this.cursor,
status: status ?? this.status,
invoiceNumber: invoiceNumber ?? this.invoiceNumber,
eventName: eventName ?? this.eventName,
count: count ?? this.count,
value: value ?? this.value,
date: date ?? this.date,
dueAt: date ?? this.dueAt,
);
}
InvoiceListEntity.fromModel(InvoiceModel model, {this.cursor})
: id = model.id,
poNumber = model.event?.purchaseOrder,
status = model.status,
invoiceNumber = model.event?.id ?? '',
eventName = model.event!.name,
count = model.items?.length?? 0,
value = (model.total ?? 0.0),
date = DateTime.parse(model.event?.date ?? ''),
dueAt = DateTime.parse(model.dueAt ?? ''),
invoiceModel = model;
InvoiceListEntity.empty()
: id = 'INV-20250113-12345',
status = InvoiceStatus.open,
invoiceNumber = 'INV-20250113-12345',
eventName = 'Event Name',
count = 16,
value = 1230,
poNumber = 'PO-123456',
date = DateTime.now(),
dueAt = DateTime.now(),
invoiceModel = null;
}

View File

@@ -0,0 +1,28 @@
import 'package:krow/features/invoice/domain/invoice_entity.dart';
enum InvoiceStatusFilterType {
all,
open,
disputed,
resolved,
paid,
overdue,
verified
}
abstract class InvoicesRepository {
Stream<dynamic> get statusStream;
Future<List<InvoiceListEntity>> getInvoices(
{String? lastItemId, required InvoiceStatusFilterType statusFilter});
void dispose();
Future<void> approveInvoice({required String invoiceId});
Future<void> disputeInvoice({
required String invoiceId,
required String reason,
required String comment,
});
}