feat: Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:krow/features/profile/benefits/domain/benefits_repository.dart';
|
||||
import 'package:krow/features/profile/benefits/domain/entities/benefit_entity.dart';
|
||||
import 'package:krow/features/profile/benefits/domain/entities/benefit_record_entity.dart';
|
||||
|
||||
@Injectable(as: BenefitsRepository)
|
||||
class BenefitsRepositoryImpl implements BenefitsRepository {
|
||||
static final _benefitsMock = [
|
||||
BenefitEntity(
|
||||
name: 'Sick Leave',
|
||||
requirement: 'You need at least 8 hours to request sick leave',
|
||||
requiredHours: 40,
|
||||
currentHours: 10,
|
||||
isClaimed: false,
|
||||
info: 'Listed certificates are mandatory for employees. If the employee '
|
||||
'does not have the complete certificates, they can’t proceed with '
|
||||
'their registration.',
|
||||
history: [
|
||||
BenefitRecordEntity(
|
||||
createdAt: DateTime(2024, 6, 14),
|
||||
status: RecordStatus.submitted,
|
||||
),
|
||||
BenefitRecordEntity(
|
||||
createdAt: DateTime(2023, 6, 5),
|
||||
status: RecordStatus.submitted,
|
||||
),
|
||||
BenefitRecordEntity(
|
||||
createdAt: DateTime(2019, 6, 4),
|
||||
status: RecordStatus.submitted,
|
||||
),
|
||||
BenefitRecordEntity(
|
||||
createdAt: DateTime(2018, 6, 1),
|
||||
status: RecordStatus.submitted,
|
||||
),
|
||||
BenefitRecordEntity(
|
||||
createdAt: DateTime(2017, 6, 24),
|
||||
status: RecordStatus.submitted,
|
||||
),
|
||||
BenefitRecordEntity(
|
||||
createdAt: DateTime(2016, 6, 15),
|
||||
status: RecordStatus.submitted,
|
||||
),
|
||||
BenefitRecordEntity(
|
||||
createdAt: DateTime(2015, 6, 6),
|
||||
status: RecordStatus.submitted,
|
||||
),
|
||||
],
|
||||
),
|
||||
const BenefitEntity(
|
||||
name: 'Vacation',
|
||||
requirement: 'You need 40 hours to claim vacation pay',
|
||||
requiredHours: 40,
|
||||
currentHours: 40,
|
||||
isClaimed: false,
|
||||
info: 'Listed certificates are mandatory for employees. If the employee '
|
||||
'does not have the complete certificates, they can’t proceed with '
|
||||
'their registration.',
|
||||
history: [],
|
||||
),
|
||||
const BenefitEntity(
|
||||
name: 'Holidays',
|
||||
requirement: 'Pay holidays: Thanksgiving, Christmas, New Year',
|
||||
requiredHours: 24,
|
||||
currentHours: 1,
|
||||
isClaimed: false,
|
||||
info: 'Listed certificates are mandatory for employees. If the employee '
|
||||
'does not have the complete certificates, they can’t proceed with '
|
||||
'their registration.',
|
||||
history: [],
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
Future<List<BenefitEntity>> getStaffBenefits() async {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
return _benefitsMock;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<BenefitEntity?> requestBenefit({
|
||||
required BenefitEntity benefit,
|
||||
}) async {
|
||||
if (benefit.currentHours != benefit.requiredHours || benefit.isClaimed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
|
||||
return benefit.copyWith(isClaimed: true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import 'package:krow/features/profile/benefits/domain/entities/benefit_entity.dart';
|
||||
|
||||
abstract interface class BenefitsRepository {
|
||||
Future<List<BenefitEntity>> getStaffBenefits();
|
||||
|
||||
Future<BenefitEntity?> requestBenefit({required BenefitEntity benefit});
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import 'dart:async';
|
||||
|
||||
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/state_status.dart';
|
||||
import 'package:krow/features/profile/benefits/domain/benefits_repository.dart';
|
||||
import 'package:krow/features/profile/benefits/domain/entities/benefit_entity.dart';
|
||||
|
||||
part 'benefits_event.dart';
|
||||
|
||||
part 'benefits_state.dart';
|
||||
|
||||
class BenefitsBloc extends Bloc<BenefitsEvent, BenefitsState> {
|
||||
BenefitsBloc() : super(const BenefitsState()) {
|
||||
on<InitializeBenefits>((event, emit) async {
|
||||
emit(state.copyWith(status: StateStatus.loading));
|
||||
|
||||
final benefits = await _repository.getStaffBenefits();
|
||||
|
||||
emit(state.copyWith(status: StateStatus.idle, benefits: benefits));
|
||||
});
|
||||
|
||||
on<SendBenefitRequest>((event, emit) async {
|
||||
emit(state.copyWith(status: StateStatus.loading));
|
||||
final result = await _repository.requestBenefit(benefit: event.benefit);
|
||||
|
||||
int index = -1;
|
||||
List<BenefitEntity>? updatedBenefits;
|
||||
if (result != null) {
|
||||
index = state.benefits.indexWhere(
|
||||
(benefit) => benefit.name == result.name,
|
||||
);
|
||||
|
||||
if (index >= 0) {
|
||||
updatedBenefits = List.from(state.benefits)..[index] = result;
|
||||
}
|
||||
}
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
benefits: updatedBenefits,
|
||||
status: StateStatus.idle,
|
||||
),
|
||||
);
|
||||
event.requestCompleter.complete(result ?? event.benefit);
|
||||
});
|
||||
}
|
||||
|
||||
final BenefitsRepository _repository = getIt<BenefitsRepository>();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
part of 'benefits_bloc.dart';
|
||||
|
||||
@immutable
|
||||
sealed class BenefitsEvent {
|
||||
const BenefitsEvent();
|
||||
}
|
||||
|
||||
class InitializeBenefits extends BenefitsEvent {
|
||||
const InitializeBenefits();
|
||||
}
|
||||
|
||||
class SendBenefitRequest extends BenefitsEvent {
|
||||
const SendBenefitRequest({
|
||||
required this.benefit,
|
||||
required this.requestCompleter,
|
||||
});
|
||||
|
||||
final BenefitEntity benefit;
|
||||
final Completer<BenefitEntity> requestCompleter;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
part of 'benefits_bloc.dart';
|
||||
|
||||
@immutable
|
||||
class BenefitsState {
|
||||
const BenefitsState({
|
||||
this.status = StateStatus.idle,
|
||||
this.benefits = const [],
|
||||
this.exception,
|
||||
});
|
||||
|
||||
final StateStatus status;
|
||||
final List<BenefitEntity> benefits;
|
||||
final Exception? exception;
|
||||
|
||||
BenefitsState copyWith({
|
||||
StateStatus? status,
|
||||
List<BenefitEntity>? benefits,
|
||||
Exception? exception,
|
||||
}) {
|
||||
return BenefitsState(
|
||||
status: status ?? this.status,
|
||||
benefits: benefits ?? this.benefits,
|
||||
exception: exception,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:krow/features/profile/benefits/domain/entities/benefit_record_entity.dart';
|
||||
|
||||
@immutable
|
||||
class BenefitEntity {
|
||||
const BenefitEntity({
|
||||
required this.name,
|
||||
required this.requirement,
|
||||
required this.requiredHours,
|
||||
required this.currentHours,
|
||||
required this.isClaimed,
|
||||
required this.info,
|
||||
required this.history,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String requirement;
|
||||
final int requiredHours;
|
||||
final int currentHours;
|
||||
final bool isClaimed;
|
||||
final String info;
|
||||
final List<BenefitRecordEntity> history;
|
||||
|
||||
double get progress {
|
||||
final progress = currentHours / requiredHours;
|
||||
return progress > 1 ? 1 : progress;
|
||||
}
|
||||
|
||||
BenefitEntity copyWith({
|
||||
String? name,
|
||||
String? requirement,
|
||||
int? requiredHours,
|
||||
int? currentHours,
|
||||
bool? isClaimed,
|
||||
String? info,
|
||||
List<BenefitRecordEntity>? history,
|
||||
}) {
|
||||
return BenefitEntity(
|
||||
name: name ?? this.name,
|
||||
requirement: requirement ?? this.requirement,
|
||||
requiredHours: requiredHours ?? this.requiredHours,
|
||||
currentHours: currentHours ?? this.currentHours,
|
||||
isClaimed: isClaimed ?? this.isClaimed,
|
||||
info: info ?? this.info,
|
||||
history: history ?? this.history,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
@immutable
|
||||
class BenefitRecordEntity {
|
||||
const BenefitRecordEntity({required this.createdAt, required this.status});
|
||||
|
||||
final DateTime createdAt;
|
||||
final RecordStatus status;
|
||||
}
|
||||
|
||||
enum RecordStatus { pending, submitted }
|
||||
@@ -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],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user