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,44 @@
import 'package:injectable/injectable.dart';
import 'package:krow/core/application/clients/api/api_client.dart';
import 'package:krow/features/check_list/data/gql.dart';
@injectable
class CheckListApiProvider {
CheckListApiProvider(this._apiClient);
static const _verificationListKey = 'verification_check_list';
final ApiClient _apiClient;
Stream<Map<String, dynamic>> fetchCheckListWithCache() async* {
await for (var result
in _apiClient.queryWithCache(schema: getCheckListQuery)) {
if (result == null || result.data == null) continue;
if (result.hasException) {
throw Exception(result.exception.toString());
}
if (result.data?[_verificationListKey] == null) continue;
yield result.data?[_verificationListKey] ?? {};
}
}
Future<Map<String, dynamic>> fetchCheckList() async {
final result = await _apiClient.query(schema: getCheckListQuery);
if (result.hasException) {
throw Exception(result.exception.toString());
}
return result.data?[_verificationListKey] ?? {};
}
Future<void> submitVerification() async {
var result = await _apiClient.mutate(schema: submitVerificationMutation);
if (result.hasException) {
throw Exception(result.exception.toString());
}
}
}

View File

@@ -0,0 +1,25 @@
import 'package:injectable/injectable.dart';
import 'package:krow/features/check_list/data/check_list_api_provider.dart';
import 'package:krow/features/check_list/domain/check_list_repository.dart';
@Injectable(as: CheckListRepository)
class CheckListRepositoryImpl implements CheckListRepository {
final CheckListApiProvider remoteDataSource;
CheckListRepositoryImpl({required this.remoteDataSource});
@override
Stream<Map<String, dynamic>> getCheckList() {
return remoteDataSource.fetchCheckListWithCache();
}
@override
Future<Map<String, dynamic>> getCheckListUpdate() {
return remoteDataSource.fetchCheckList();
}
@override
Future<void> submitVerification() async {
return remoteDataSource.submitVerification();
}
}

View File

@@ -0,0 +1,23 @@
const String getCheckListQuery = '''
query checkList{
verification_check_list {
personal_info
emergency_contacts
roles
equipments
uniforms
working_areas
bank_account
certificates
schedule
}
}
''';
const String submitVerificationMutation = '''
mutation submitVerification{
submit_staff_profile_for_verification {
status
}
}
''';

View File

@@ -0,0 +1,99 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow/core/application/di/injectable.dart';
import 'package:krow/core/application/routing/routes.gr.dart';
import 'package:krow/features/check_list/domain/bloc/check_list_event.dart';
import 'package:krow/features/check_list/domain/bloc/check_list_state.dart';
import 'package:krow/features/check_list/domain/check_list_repository.dart';
import 'package:krow/features/profile/role_kit/domain/staff_role_kit_repository_impl.dart';
class CheckListBloc extends Bloc<CheckListEvent, CheckListState> {
CheckListBloc() : super(const CheckListState()) {
on<CheckListEventFetch>(_onFetch);
on<CheckForListUpdateEvent>(_onCheckForUpdate);
on<CheckListEventSubmit>(_onSubmit);
on<CheckListEventAgree>(_onAgree);
}
void _onFetch(CheckListEventFetch event, Emitter<CheckListState> emit) async {
if (state.checkListItems.isNotEmpty) return;
await for (var checkList in getIt<CheckListRepository>().getCheckList()) {
emit(state.copyWith(checkListItems: _parseCheckListData(checkList)));
}
}
void _onCheckForUpdate(
CheckForListUpdateEvent event,
Emitter<CheckListState> emit,
) async {
if (event.editedItem.error == null) return;
final result = await getIt<CheckListRepository>().getCheckListUpdate();
emit(
state.copyWith(
checkListItems: _parseCheckListData(result),
),
);
}
void _onSubmit(
CheckListEventSubmit event,
Emitter<CheckListState> emit,
) async {
emit(state.copyWith(loading: true));
try {
await getIt<CheckListRepository>().submitVerification();
} finally {
emit(state.copyWith(loading: false));
}
emit(state.copyWith(isSubmitted: true));
}
void _onAgree(CheckListEventAgree event, Emitter<CheckListState> emit) {
emit(state.copyWith(isAgree: !state.isAgree));
}
List<CheckListItemState> _parseCheckListData(Map<String, dynamic> checkList) {
return [
CheckListItemState(
title: 'Personal Information'.tr(),
error: checkList['personal_info'],
route: PersonalInfoRoute(isInEditMode: true)),
CheckListItemState(
title: 'Emergency contact'.tr(),
error: checkList['emergency_contacts'],
route: EmergencyContactsRoute()),
CheckListItemState(
title: 'Roles'.tr(), error: checkList['roles'], route: RoleRoute()),
if (checkList.containsKey('equipments'))
CheckListItemState(
title: 'Equipment'.tr(),
error: checkList['equipments'],
route: RoleKitFlowRoute(roleKitType: RoleKitType.equipment)),
if (checkList.containsKey('uniforms'))
CheckListItemState(
title: 'Uniform'.tr(),
error: checkList['uniforms'],
route: RoleKitFlowRoute(roleKitType: RoleKitType.uniform)),
CheckListItemState(
title: 'Working Area'.tr(),
error: checkList['working_areas'],
route: WorkingAreaRoute()),
CheckListItemState(
title: 'Availability'.tr(),
error: checkList['schedule'],
route: ScheduleRoute()),
CheckListItemState(
title: 'Bank Account'.tr(),
error: checkList['bank_account'],
route: const BankAccountFlowRoute()),
CheckListItemState(title: 'Wages form'.tr(), error: checkList['']),
CheckListItemState(
title: 'Certificates'.tr(),
error: checkList['certificates'],
route: const CertificatesRoute()),
CheckListItemState(title: 'Background check'.tr(), error: checkList['']),
];
}
}

View File

@@ -0,0 +1,25 @@
import 'package:flutter/foundation.dart';
import 'package:krow/features/check_list/domain/bloc/check_list_state.dart';
@immutable
sealed class CheckListEvent {
const CheckListEvent();
}
class CheckListEventFetch extends CheckListEvent {
const CheckListEventFetch();
}
class CheckForListUpdateEvent extends CheckListEvent {
final CheckListItemState editedItem;
const CheckForListUpdateEvent({required this.editedItem});
}
class CheckListEventSubmit extends CheckListEvent {
const CheckListEventSubmit();
}
class CheckListEventAgree extends CheckListEvent {
const CheckListEventAgree();
}

View File

@@ -0,0 +1,46 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/foundation.dart';
@immutable
class CheckListState {
final List<CheckListItemState> checkListItems;
final bool isSubmitted;
final bool isAgree;
final bool isSubmitEnabled;
final bool loading;
const CheckListState({
this.checkListItems = const [],
this.isSubmitted = false,
this.isAgree = false,
this.loading = false,
this.isSubmitEnabled = false,
});
CheckListState copyWith({
List<CheckListItemState>? checkListItems,
bool? isSubmitted,
bool? isAgree,
bool? loading,
bool? isSubmitEnabled,
}) {
return CheckListState(
checkListItems: checkListItems ?? this.checkListItems,
isSubmitted: isSubmitted ?? this.isSubmitted,
isAgree: isAgree ?? this.isAgree,
loading: loading ?? false,
isSubmitEnabled: isSubmitEnabled ?? this.isSubmitEnabled,
);
}
bool get hasErrors => checkListItems.any((element) => element.error != null);
}
@immutable
class CheckListItemState {
final String title;
final String? error;
final PageRouteInfo? route;
const CheckListItemState({required this.title, this.route, this.error});
}

View File

@@ -0,0 +1,7 @@
abstract class CheckListRepository {
Stream<Map<String,dynamic>> getCheckList();
Future<Map<String, dynamic>> getCheckListUpdate();
Future<void> submitVerification();
}

View File

@@ -0,0 +1,12 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
@RoutePage()
class CheckListFlowScreen extends StatelessWidget {
const CheckListFlowScreen({super.key});
@override
Widget build(BuildContext context) {
return const AutoRouter();
}
}

View File

@@ -0,0 +1,176 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gap/gap.dart';
import 'package:krow/core/application/di/injectable.dart';
import 'package:krow/core/application/routing/routes.gr.dart';
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
import 'package:krow/core/presentation/styles/theme.dart';
import 'package:krow/core/presentation/widgets/ui_kit/check_box.dart';
import 'package:krow/core/presentation/widgets/ui_kit/kw_app_bar.dart';
import 'package:krow/core/presentation/widgets/ui_kit/kw_button.dart';
import 'package:krow/core/sevices/auth_state_service/auth_service.dart';
import 'package:krow/features/check_list/domain/bloc/check_list_bloc.dart';
import 'package:krow/features/check_list/domain/bloc/check_list_event.dart';
import 'package:krow/features/check_list/domain/bloc/check_list_state.dart';
import 'package:krow/features/check_list/presentation/widgets/check_list_display_widget.dart';
@RoutePage()
class CheckListScreen extends StatelessWidget implements AutoRouteWrapper {
const CheckListScreen({super.key});
@override
Widget wrappedRoute(BuildContext context) {
return BlocProvider(
create: (_) => CheckListBloc()..add(const CheckListEventFetch()),
child: this,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: KwAppBar(),
body: BlocListener<CheckListBloc, CheckListState>(
listenWhen: (previous, current) =>
previous.isSubmitted != current.isSubmitted,
listener: (context, state) {
if (state.isSubmitted) {
context.router.push(const WaitingValidationRoute());
}
},
child: SafeArea(
top: false,
child: ListView(
primary: false,
padding: const EdgeInsets.all(16),
children: [
Text(
'finalize_your_profile'.tr(),
style: AppTextStyles.headingH1,
),
const SizedBox(height: 8),
Text(
'check_profile_verification'.tr(),
style: AppTextStyles.bodyMediumReg
.copyWith(color: AppColors.blackGray),
),
const SizedBox(height: 16),
const CheckListDisplayWidget(),
const Gap(36),
const _AgreementWidget(),
const Gap(36),
const _SubmitButtonWidget(),
const Gap(36),
RichText(
textAlign: TextAlign.center,
text: TextSpan(
text: '${'not_you'.tr()} ',
style: AppTextStyles.bodyMediumReg
.copyWith(color: AppColors.blackGray),
children: [
TextSpan(
text: 'log_out'.tr(),
style: AppTextStyles.bodyMediumSmb.copyWith(
decoration: TextDecoration.underline,
),
recognizer: TapGestureRecognizer()
..onTap = () {
getIt<AuthService>().logout();
context.router.replace(const AuthFlowRoute());
},
),
],
),
)
],
),
),
),
);
}
}
class _AgreementWidget extends StatelessWidget {
const _AgreementWidget();
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
context.read<CheckListBloc>().add(const CheckListEventAgree());
},
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
BlocSelector<CheckListBloc, CheckListState, bool>(
selector: (state) => state.isAgree,
builder: (context, isAgree) {
return KWCheckBox(value: isAgree);
},
),
const Gap(8),
Expanded(
child: RichText(
text: TextSpan(
children: [
TextSpan(
text: '${'i_agree_to_the'.tr()} ',
style: AppTextStyles.bodyMediumMed,
),
TextSpan(
text: 'terms_and_conditions'.tr(),
style: AppTextStyles.bodyMediumSmb.copyWith(
decoration: TextDecoration.underline,
),
recognizer: TapGestureRecognizer()
..onTap = () {
//TODO(Sleep): Handle Terms and Conditions tap
},
),
TextSpan(
text: ' ${'and'.tr()} ',
style: AppTextStyles.bodyMediumMed,
),
TextSpan(
text: 'privacy_policy'.tr(),
style: AppTextStyles.bodyMediumSmb.copyWith(
decoration: TextDecoration.underline,
),
recognizer: TapGestureRecognizer()
..onTap = () {
//TODO(Sleep): Handle Privacy Policy tap
},
),
],
),
),
)
],
),
);
}
}
class _SubmitButtonWidget extends StatelessWidget {
const _SubmitButtonWidget();
@override
Widget build(BuildContext context) {
return BlocBuilder<CheckListBloc, CheckListState>(
buildWhen: (previous, current) {
return previous.isAgree != current.isAgree ||
previous.hasErrors != current.hasErrors;
}, builder: (context, state) {
return KwButton.primary(
disabled: !state.isAgree || state.hasErrors,
label: '${'submit_profile_verification'.tr()} ',
onPressed: () {
context.read<CheckListBloc>().add(const CheckListEventSubmit());
},
);
});
}
}

View File

@@ -0,0 +1,127 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:gap/gap.dart';
import 'package:krow/core/data/static/contacts_data.dart';
import 'package:krow/core/presentation/gen/assets.gen.dart';
import 'package:krow/core/presentation/styles/kw_box_decorations.dart';
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
import 'package:krow/core/presentation/styles/theme.dart';
import 'package:krow/core/presentation/widgets/contact_icon_button.dart';
import 'package:krow/core/presentation/widgets/ui_kit/kw_app_bar.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:whatsapp_unilink/whatsapp_unilink.dart';
@RoutePage()
class WaitingValidationScreen extends StatefulWidget {
const WaitingValidationScreen({super.key});
@override
State<WaitingValidationScreen> createState() =>
_WaitingValidationScreenState();
}
class _WaitingValidationScreenState extends State<WaitingValidationScreen> {
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned.fill(
child: Container(
color: AppColors.bgColorDark,
child: SvgPicture.asset(
Assets.images.bg.path,
fit: BoxFit.cover,
),
),
),
Scaffold(
backgroundColor: Colors.transparent,
appBar: buildAppBar(),
body: SafeArea(
child: Column(
children: [
Container(
margin: const EdgeInsets.only(top: 120, left: 16, right: 16),
padding: const EdgeInsets.all(12),
decoration: KwBoxDecorations.primaryDark,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Assets.images.waitingValidation.coffeeBreak.svg(),
const Gap(24),
Text(
'your_account_is_being_verified'.tr(),
textAlign: TextAlign.center,
style: AppTextStyles.headingH1.copyWith(
color: Colors.white,
),
),
const Gap(8),
Text(
'waiting_for_email_interview'.tr(),
style: AppTextStyles.bodyMediumReg.copyWith(
color: AppColors.textPrimaryInverted,
),
),
const Gap(24),
Text(
'${'contact_support_via'.tr()}:',
style: AppTextStyles.bodyMediumMed.copyWith(
color: AppColors.primaryYellow,
),
),
const Gap(12),
buildContactsGroup()
],
),
),
],
),
),
),
],
);
}
Row buildContactsGroup() {
return Row(
spacing: 24,
mainAxisAlignment: MainAxisAlignment.center,
children: [
ContactIconButton(
icon: Assets.images.waitingValidation.call,
onTap: () {
launchUrlString('tel:${ContactsData.supportPhone}');
},
),
ContactIconButton(
icon: Assets.images.waitingValidation.sms,
onTap: () {
launchUrlString('mailto:${ContactsData.supportEmail}');
},
),
ContactIconButton(
icon: Assets.images.waitingValidation.whatsapp,
onTap: () {
const link = WhatsAppUnilink(
phoneNumber: ContactsData.supportPhone,
text: 'Hey!',
);
launchUrlString(link.toString());
},
),
],
);
}
AppBar buildAppBar() {
return KwAppBar(
backgroundColor: Colors.transparent,
iconColorStyle: AppBarIconColorStyle.inverted,
showNotification: false,
contentColor: AppColors.primaryYellow,
);
}
}

View File

@@ -0,0 +1,51 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow/core/presentation/widgets/ui_kit/check_box.dart';
import 'package:krow/core/presentation/widgets/ui_kit/check_box_card.dart';
import 'package:krow/features/check_list/domain/bloc/check_list_bloc.dart';
import 'package:krow/features/check_list/domain/bloc/check_list_event.dart';
import 'package:krow/features/check_list/domain/bloc/check_list_state.dart';
class CheckListDisplayWidget extends StatelessWidget {
const CheckListDisplayWidget({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<CheckListBloc, CheckListState>(
buildWhen: (previous, current) {
return previous.checkListItems != current.checkListItems;
},
builder: (context, state) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
for (final item in state.checkListItems)
CheckBoxCard(
title: item.title,
isChecked: item.error == null,
checkBoxStyle: item.error != null
? CheckBoxStyle.black
: CheckBoxStyle.green,
padding: const EdgeInsets.only(top: 8),
trailing: true,
errorMessage: item.error,
onTap: () {
final route = item.route;
if (route == null) return;
context.router.push<void>(route).then((_) {
if (!context.mounted) return;
context
.read<CheckListBloc>()
.add(CheckForListUpdateEvent(editedItem: item));
});
},
),
],
);
},
);
}
}