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,95 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gap/gap.dart';
import 'package:krow/core/data/enums/state_status.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/kw_app_bar.dart';
import 'package:krow/core/presentation/widgets/ui_kit/kw_loading_overlay.dart';
import 'package:krow/features/profile/benefits/domain/bloc/benefits_bloc.dart';
import 'package:krow/features/profile/benefits/presentation/widgets/benefit_card_widget.dart';
@RoutePage()
class BenefitsScreen extends StatefulWidget implements AutoRouteWrapper {
const BenefitsScreen({super.key});
@override
State<BenefitsScreen> createState() => _BenefitsScreenState();
@override
Widget wrappedRoute(BuildContext context) {
return BlocProvider<BenefitsBloc>(
create: (context) => BenefitsBloc()..add(const InitializeBenefits()),
child: this,
);
}
}
class _BenefitsScreenState extends State<BenefitsScreen> {
final OverlayPortalController _controller = OverlayPortalController();
@override
Widget build(BuildContext context) {
return Scaffold(
body: KwLoadingOverlay(
controller: _controller,
child: BlocListener<BenefitsBloc, BenefitsState>(
listenWhen: (previous, current) => previous.status != current.status,
listener: (context, state) {
if (state.status == StateStatus.loading) {
_controller.show();
} else {
_controller.hide();
}
},
child: CustomScrollView(
primary: false,
slivers: [
SliverList.list(
children: [
KwAppBar(
titleText: 'your_benefits_overview'.tr(),
showNotification: true,
),
const Gap(16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
'manage_and_track_benefits'.tr(),
style: AppTextStyles.bodySmallReg.copyWith(
color: AppColors.blackGray,
),
textAlign: TextAlign.start,
),
),
],
),
SliverPadding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 120),
sliver: BlocBuilder<BenefitsBloc, BenefitsState>(
buildWhen: (current, previous) =>
current.benefits != previous.benefits,
builder: (context, state) {
return SliverList.separated(
itemCount: state.benefits.length,
separatorBuilder: (context, index) {
return const SizedBox(height: 12);
},
itemBuilder: (context, index) {
return BenefitCardWidget(
benefit: state.benefits[index],
);
},
);
},
),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,253 @@
import 'dart:async';
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow/core/presentation/gen/assets.gen.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/dialogs/kw_dialog.dart';
import 'package:krow/core/presentation/widgets/ui_kit/kw_button.dart';
import 'package:krow/features/profile/benefits/domain/bloc/benefits_bloc.dart';
import 'package:krow/features/profile/benefits/domain/entities/benefit_entity.dart';
import 'package:krow/features/profile/benefits/presentation/widgets/benefit_history_widget.dart';
class BenefitCardWidget extends StatefulWidget {
const BenefitCardWidget({super.key, required this.benefit});
final BenefitEntity benefit;
@override
State<BenefitCardWidget> createState() => _BenefitCardWidgetState();
}
class _BenefitCardWidgetState extends State<BenefitCardWidget>
with TickerProviderStateMixin {
late final AnimationController _animationController;
late BenefitEntity _benefit = widget.benefit;
Completer<BenefitEntity>? _requestCompleter;
double _progress = 0;
bool _isReady = false;
@override
void initState() {
_progress = widget.benefit.progress;
_isReady = _progress == 1;
super.initState();
_animationController = AnimationController(vsync: this);
_animationController.animateTo(
_progress,
duration: const Duration(seconds: 1),
curve: Curves.easeOut,
);
}
Future<void> _handleRequestPress() async {
_requestCompleter?.completeError(Exception('previous_aborted'.tr()));
final completer = _requestCompleter = Completer<BenefitEntity>();
this.context.read<BenefitsBloc>().add(
SendBenefitRequest(
benefit: _benefit,
requestCompleter: completer,
),
);
final benefit = await completer.future;
if (!benefit.isClaimed) return;
setState(() {
_progress = 0;
_isReady = false;
_benefit = _benefit.copyWith(currentHours: 0);
});
await _animationController.animateTo(
_progress,
duration: const Duration(seconds: 1),
curve: Curves.easeOut,
);
final context = this.context;
if (!context.mounted) return;
await KwDialog.show(
context: context,
icon: Assets.images.icons.like,
state: KwDialogState.positive,
title: 'request_submitted'.tr(),
message: 'request_submitted_message'.tr(args: [_benefit.name.toLowerCase()]),
primaryButtonLabel: 'back_to_profile'.tr(),
onPrimaryButtonPressed: (dialogContext) {
Navigator.maybePop(dialogContext);
context.maybePop();
},
);
}
@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: const BoxDecoration(
color: AppColors.grayPrimaryFrame,
borderRadius: BorderRadius.all(Radius.circular(8)),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Stack(
alignment: AlignmentDirectional.center,
children: [
AnimatedBuilder(
animation: _animationController,
builder: (context, _) {
return CircularProgressIndicator(
constraints:
BoxConstraints.tight(const Size.square(90)),
strokeWidth: 8,
strokeCap: StrokeCap.round,
backgroundColor: AppColors.bgColorLight,
color: _isReady
? AppColors.statusSuccess
: AppColors.primaryBlue,
value: _animationController.value,
);
},
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
RichText(
text: TextSpan(
text: '${_benefit.currentHours}/',
style: AppTextStyles.headingH3,
children: [
TextSpan(
text: '${_benefit.requiredHours}',
style: AppTextStyles.headingH3.copyWith(
color: _isReady
? AppColors.blackBlack
: AppColors.blackCaptionText,
),
),
],
),
),
const SizedBox(height: 4),
Text(
'hours'.tr().toLowerCase(),
style: AppTextStyles.captionReg
.copyWith(color: AppColors.blackCaptionText),
),
],
)
],
),
const SizedBox(width: 24),
Expanded(
child: Stack(
alignment: AlignmentDirectional.centerStart,
clipBehavior: Clip.none,
children: [
const SizedBox(height: 90),
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_benefit.name, style: AppTextStyles.headingH3),
const SizedBox(height: 6),
Text(
_benefit.requirement,
style: AppTextStyles.bodySmallReg.copyWith(
color: AppColors.blackGray,
),
),
],
),
Positioned(
top: -4,
right: 0,
child: Assets.images.icons.alertCircle.svg(
height: 16,
width: 16,
colorFilter: const ColorFilter.mode(
AppColors.grayStroke,
BlendMode.srcIn,
),
),
)
],
),
),
],
),
const SizedBox(height: 20),
if (_isReady)
Container(
margin: const EdgeInsets.only(bottom: 20),
padding: const EdgeInsets.all(8),
decoration: const BoxDecoration(
color: AppColors.grayWhite,
borderRadius: BorderRadius.all(Radius.circular(8)),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DecoratedBox(
decoration: const BoxDecoration(
color: AppColors.tintGreen,
shape: BoxShape.circle,
),
child: SizedBox.square(
dimension: 28,
child: Assets.images.icons.checkCircle.svg(
height: 10,
width: 10,
fit: BoxFit.scaleDown,
colorFilter: const ColorFilter.mode(
AppColors.statusSuccess,
BlendMode.srcIn,
),
),
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
_benefit.info,
style: AppTextStyles.bodyTinyMed.copyWith(
color: AppColors.statusSuccess,
),
),
),
],
),
),
BenefitHistoryWidget(benefit: _benefit),
if (_isReady)
Padding(
padding: const EdgeInsets.only(top: 20),
child: KwButton.primary(
label: '${'request_payment_for'.tr()} ${_benefit.name}',
onPressed: _handleRequestPress,
),
),
],
),
),
);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
}
}

View File

@@ -0,0 +1,123 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
import 'package:krow/core/presentation/styles/theme.dart';
import 'package:krow/features/profile/benefits/domain/entities/benefit_entity.dart';
import 'package:krow/features/profile/benefits/domain/entities/benefit_record_entity.dart';
class BenefitHistoryWidget extends StatelessWidget {
const BenefitHistoryWidget({super.key, required this.benefit});
final BenefitEntity benefit;
@override
Widget build(BuildContext context) {
return ExpansionTile(
tilePadding: const EdgeInsets.symmetric(horizontal: 12),
childrenPadding: const EdgeInsets.symmetric(horizontal: 12),
dense: true,
visualDensity: VisualDensity.compact,
collapsedShape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
backgroundColor: AppColors.graySecondaryFrame,
collapsedBackgroundColor: AppColors.graySecondaryFrame,
iconColor: AppColors.blackBlack,
collapsedIconColor: AppColors.blackBlack,
title: Text(
'${benefit.name} ${'history'.tr()}'.toUpperCase(),
style: AppTextStyles.captionBold,
),
children: [
const Divider(
thickness: 1,
color: AppColors.tintGray,
),
const SizedBox(height: 12),
if (benefit.history.isEmpty)
SizedBox(
height: 80,
child: Center(
child: Text('no_history_yet'.tr()),
),
)
else
SizedBox(
height: 168,
child: RawScrollbar(
padding: EdgeInsets.zero,
thumbVisibility: true,
trackVisibility: true,
thumbColor: AppColors.grayStroke,
trackColor: AppColors.grayTintStroke,
trackRadius: const Radius.circular(8),
radius: const Radius.circular(8),
trackBorderColor: Colors.transparent,
thickness: 5,
minOverscrollLength: 0,
child: ListView.separated(
padding: const EdgeInsetsDirectional.only(end: 10),
itemCount: benefit.history.length,
separatorBuilder: (context, index) =>
const SizedBox(height: 12),
itemBuilder: (context, index) {
return _HistoryRecordWidget(record: benefit.history[index]);
},
),
),
),
const SizedBox(height: 12),
],
);
}
}
class _HistoryRecordWidget extends StatelessWidget {
const _HistoryRecordWidget({required this.record});
static final _dateFormat = DateFormat('d MMM, yyyy');
final BenefitRecordEntity record;
@override
Widget build(BuildContext context) {
final Color color = switch (record.status) {
RecordStatus.pending => AppColors.primaryBlue,
RecordStatus.submitted => AppColors.statusSuccess,
};
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_dateFormat.format(record.createdAt),
style: AppTextStyles.bodySmallReg.copyWith(
color: AppColors.blackGray,
),
),
DecoratedBox(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(24)),
border: Border.all(color: color),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
child: Text(
switch (record.status) {
RecordStatus.pending => 'pending'.tr(),
RecordStatus.submitted => 'submitted'.tr(),
},
style: AppTextStyles.bodySmallReg.copyWith(
color: color,
),
),
),
),
],
);
}
}