You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
431 lines
14 KiB
Dart
431 lines
14 KiB
Dart
import 'package:easy_localization/easy_localization.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:hmg_patient_app_new/core/api_consts.dart';
|
|
import 'package:hmg_patient_app_new/core/app_assets.dart';
|
|
import 'package:hmg_patient_app_new/core/app_state.dart';
|
|
import 'package:hmg_patient_app_new/core/dependencies.dart';
|
|
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
|
|
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
|
|
import 'package:hmg_patient_app_new/core/utils/utils.dart';
|
|
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
|
|
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
|
|
import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart';
|
|
import 'package:hmg_patient_app_new/features/habib_wallet/models/lakum_inquiry_information_response_model.dart';
|
|
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
|
|
import 'package:hmg_patient_app_new/theme/colors.dart';
|
|
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
|
|
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
|
|
class LakumWalletDetails extends StatelessWidget {
|
|
const LakumWalletDetails({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
AppState appState = getIt.get<AppState>();
|
|
|
|
return CollapsingListView(
|
|
title: LocaleKeys.lakumPoints.tr(context: context),
|
|
child: Consumer<HabibWalletViewModel>(
|
|
builder: (context, viewModel, child) {
|
|
// Check if loading
|
|
if (viewModel.isLakumAccountInfoLoading) {
|
|
return Center(
|
|
child: Padding(
|
|
padding: EdgeInsets.all(32.w),
|
|
child: CircularProgressIndicator(
|
|
color: AppColors.primaryRedColor,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Check if account number is not available
|
|
if (viewModel.yahalaAccountNumber == "0" || viewModel.yahalaAccountNumber.isEmpty) {
|
|
return Center(
|
|
child: Padding(
|
|
padding: EdgeInsets.all(32.w),
|
|
child: LocaleKeys.lakumMsg.tr().toText14(
|
|
color: AppColors.greyTextColor,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Get all transactions
|
|
List<PointsDetails> transactions = viewModel.getAllLakumTransactions();
|
|
|
|
// Display main UI
|
|
return SingleChildScrollView(
|
|
child: Padding(
|
|
padding: EdgeInsets.all(16.w),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
// Main Lakum Card with gradient and user info
|
|
_buildLakumMainCard(viewModel.lakumAccountInfo, appState),
|
|
SizedBox(height: 24.h),
|
|
|
|
// Redeem Button
|
|
_buildRedeemButton(context),
|
|
SizedBox(height: 24.h),
|
|
|
|
// Transaction List Header
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
LocaleKeys.transactions.tr(context: context).toText16(isBold: true),
|
|
if (transactions.isNotEmpty) "${transactions.length} Records".toText12(color: AppColors.greyTextColor),
|
|
],
|
|
),
|
|
SizedBox(height: 12.h),
|
|
|
|
// Transaction List Container
|
|
if (transactions.isEmpty) _buildEmptyTransactions() else _buildTransactionsList(transactions),
|
|
|
|
SizedBox(height: 24.h),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildLakumMainCard(LakumInquiryInformationResponseModel accountInfo, AppState appState) {
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
AppColors.habibPharmacyColor,
|
|
AppColors.habibPharmacyColor.withAlpha(220),
|
|
],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(24.r),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: AppColors.habibPharmacyColor.withAlpha(76),
|
|
blurRadius: 16,
|
|
offset: Offset(0, 8),
|
|
),
|
|
],
|
|
),
|
|
child: Padding(
|
|
padding: EdgeInsets.all(20.w),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Top Row: User Info and Grid Icon
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
"${appState.getAuthenticatedUser()?.firstName ?? ''} ${appState.getAuthenticatedUser()?.lastName ?? ''}".toText18(
|
|
isBold: true,
|
|
color: Colors.white,
|
|
),
|
|
SizedBox(height: 4.h),
|
|
(accountInfo.accountNumber ?? 0).toString().toText14(
|
|
color: Colors.white.withAlpha(230),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Utils.buildImgWithAssets(
|
|
icon: AppAssets.lakumLogoWhite,
|
|
width: 80.h,
|
|
height: 80.h,
|
|
),
|
|
],
|
|
),
|
|
// SizedBox(height: 24.h),
|
|
LocaleKeys.lakumPoints.tr().toText18(color: Colors.white.withAlpha(230), isBold: true),
|
|
SizedBox(height: 8.h),
|
|
|
|
// Points Balance
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
(accountInfo.pointsBalance?.toString() ?? "0").toText44(
|
|
isBold: true,
|
|
color: Colors.white,
|
|
),
|
|
SizedBox(width: 8.w),
|
|
LocaleKeys.points
|
|
.tr()
|
|
.toText18(
|
|
color: Colors.white.withAlpha(230),
|
|
isBold: true,
|
|
)
|
|
.paddingOnly(bottom: 8.h),
|
|
],
|
|
),
|
|
SizedBox(height: 4.h),
|
|
|
|
// Balance Amount in SAR
|
|
"≈ ${accountInfo.pointsBalanceAmount?.toStringAsFixed(2) ?? "0.00"} SAR".toText18(
|
|
color: Colors.white.withAlpha(204),
|
|
),
|
|
SizedBox(height: 20.h),
|
|
|
|
// Stats Row
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildStatChip(
|
|
LocaleKeys.gained.tr(),
|
|
accountInfo.gainedPoints?.toString() ?? "0",
|
|
Icons.arrow_circle_up,
|
|
),
|
|
),
|
|
SizedBox(width: 8.w),
|
|
Expanded(
|
|
child: _buildStatChip(
|
|
LocaleKeys.consumed.tr(),
|
|
accountInfo.consumedPoints?.toString() ?? "0",
|
|
Icons.arrow_circle_down,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatChip(String label, String value, IconData icon) {
|
|
return Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.h),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withAlpha(51),
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(icon, color: Colors.white, size: 16.f),
|
|
SizedBox(width: 6.w),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
value.toText14(color: Colors.white, isBold: true),
|
|
label.toText10(color: Colors.white.withAlpha(204)),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildRedeemButton(BuildContext context) {
|
|
return CustomButton(
|
|
height: 46.h,
|
|
icon: AppAssets.redeem_icon,
|
|
iconColor: AppColors.whiteColor,
|
|
iconSize: 24.h,
|
|
backgroundColor: AppColors.habibPharmacyColor,
|
|
textColor: AppColors.whiteColor,
|
|
text: LocaleKeys.redeem.tr(context: context),
|
|
borderWidth: 0.w,
|
|
fontWeight: FontWeight.w600,
|
|
borderColor: AppColors.habibPharmacyColor.withAlpha(15),
|
|
padding: EdgeInsets.fromLTRB(4, 0, 12, 0),
|
|
fontSize: 14.f,
|
|
onPressed: () {
|
|
Uri uri = Uri.parse(PHARMACY_REDIRECT_URL);
|
|
launchUrl(uri, mode: LaunchMode.externalApplication);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildEmptyTransactions() {
|
|
return Container(
|
|
padding: EdgeInsets.all(32.w),
|
|
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
|
color: AppColors.whiteColor,
|
|
borderRadius: 16.r,
|
|
hasShadow: false,
|
|
),
|
|
child: Center(
|
|
child: Column(
|
|
children: [
|
|
Icon(Icons.receipt_long_outlined, size: 48.f, color: AppColors.greyTextColor),
|
|
SizedBox(height: 12.h),
|
|
"No transactions found".toText14(
|
|
color: AppColors.greyTextColor,
|
|
isCenter: true,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTransactionsList(List<PointsDetails> transactions) {
|
|
return Container(
|
|
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
|
color: AppColors.whiteColor,
|
|
borderRadius: 16.r,
|
|
hasShadow: false,
|
|
),
|
|
child: ListView.separated(
|
|
padding: EdgeInsets.all(16.w),
|
|
shrinkWrap: true,
|
|
physics: NeverScrollableScrollPhysics(),
|
|
itemCount: transactions.length,
|
|
separatorBuilder: (context, index) => Divider(
|
|
color: AppColors.dividerColor,
|
|
height: 24.h,
|
|
),
|
|
itemBuilder: (context, index) {
|
|
return _buildTransactionItem(transactions[index]);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTransactionItem(PointsDetails transaction) {
|
|
// Determine if points were gained or used
|
|
bool isGained = transaction.operationType?.toLowerCase() == 'gain' || transaction.operationType?.toLowerCase() == 'credit' || (transaction.points ?? 0) > 0;
|
|
|
|
Color transactionColor = isGained ? AppColors.habibPharmacyColor : AppColors.primaryRedColor;
|
|
String pointsText = "${isGained ? '+' : '-'}${transaction.points?.abs() ?? 0}";
|
|
|
|
// Format date
|
|
String formattedDate = "";
|
|
if (transaction.transactionDate != null) {
|
|
try {
|
|
formattedDate = DateUtil.formatDateToDate(
|
|
DateUtil.convertStringToDate(transaction.transactionDate!),
|
|
false,
|
|
);
|
|
} catch (e) {
|
|
formattedDate = transaction.transactionDate ?? "";
|
|
}
|
|
}
|
|
|
|
return Row(
|
|
children: [
|
|
// Transaction Icon
|
|
Container(
|
|
width: 44.w,
|
|
height: 44.h,
|
|
decoration: BoxDecoration(
|
|
color: transactionColor.withAlpha(25),
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
child: Icon(
|
|
isGained ? Icons.add_rounded : Icons.remove_rounded,
|
|
color: transactionColor,
|
|
size: 24.f,
|
|
),
|
|
),
|
|
SizedBox(width: 12.w),
|
|
|
|
// Transaction Details
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
(transaction.subTransactionTypeDescription ?? transaction.operationType ?? "Transaction").toText14(
|
|
isBold: true,
|
|
maxlines: 2,
|
|
),
|
|
SizedBox(height: 2.h),
|
|
formattedDate.toText12(color: AppColors.greyTextColor),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Points Value
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
pointsText.toText18(
|
|
isBold: true,
|
|
color: transactionColor,
|
|
),
|
|
if (transaction.amount != null && transaction.amount! > 0)
|
|
"${transaction.amount?.toStringAsFixed(2) ?? "0.00"} SAR".toText11(
|
|
color: AppColors.greyTextColor,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
// Custom painter for wavy background pattern
|
|
class _WavyBackgroundPainter extends CustomPainter {
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
// Base color from AppColors
|
|
final baseColor = AppColors.habibPharmacyColor;
|
|
|
|
final paint = Paint()
|
|
..color = Colors.white.withAlpha(25)
|
|
..style = PaintingStyle.fill;
|
|
|
|
// Draw multiple wave patterns
|
|
final path1 = Path();
|
|
path1.moveTo(0, size.height * 0.3);
|
|
path1.quadraticBezierTo(
|
|
size.width * 0.25,
|
|
size.height * 0.2,
|
|
size.width * 0.5,
|
|
size.height * 0.3,
|
|
);
|
|
path1.quadraticBezierTo(
|
|
size.width * 0.75,
|
|
size.height * 0.4,
|
|
size.width,
|
|
size.height * 0.3,
|
|
);
|
|
path1.lineTo(size.width, 0);
|
|
path1.lineTo(0, 0);
|
|
path1.close();
|
|
canvas.drawPath(path1, paint);
|
|
|
|
// Second wave with darker shade
|
|
paint.color = Colors.black.withAlpha(15);
|
|
final path2 = Path();
|
|
path2.moveTo(0, size.height * 0.6);
|
|
path2.quadraticBezierTo(
|
|
size.width * 0.3,
|
|
size.height * 0.5,
|
|
size.width * 0.6,
|
|
size.height * 0.6,
|
|
);
|
|
path2.quadraticBezierTo(
|
|
size.width * 0.8,
|
|
size.height * 0.7,
|
|
size.width,
|
|
size.height * 0.6,
|
|
);
|
|
path2.lineTo(size.width, size.height);
|
|
path2.lineTo(0, size.height);
|
|
path2.close();
|
|
canvas.drawPath(path2, paint);
|
|
|
|
// Decorative circles with white overlay
|
|
paint.color = Colors.white.withAlpha(20);
|
|
canvas.drawCircle(Offset(size.width * 0.85, size.height * 0.15), 30, paint);
|
|
paint.color = Colors.white.withAlpha(10);
|
|
canvas.drawCircle(Offset(size.width * 0.15, size.height * 0.75), 40, paint);
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
|
}
|