Lakum integration completed
parent
40a0ecdda4
commit
9d04133b8d
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,430 @@
|
||||
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;
|
||||
}
|
||||
Loading…
Reference in New Issue