diff --git a/assets/images/png/lakum_logo_white.png b/assets/images/png/lakum_logo_white.png new file mode 100644 index 00000000..b05c90a2 Binary files /dev/null and b/assets/images/png/lakum_logo_white.png differ diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 7f8e175c..f11de07f 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -420,6 +420,7 @@ class AppAssets { static const String maleIcon = '$pngBasePath/male_icon.png'; static const String femaleIcon = '$pngBasePath/female_icon.png'; static const String lakumLogo = '$pngBasePath/lakum_logo.png'; + static const String lakumLogoWhite = '$pngBasePath/lakum_logo_white.png'; static const String fullBodyFrontMale = '$pngBasePath/full_body_front_male.png'; static const String fullBodyBackMale = '$pngBasePath/full_body_back_male.png'; diff --git a/lib/features/habib_wallet/habib_wallet_view_model.dart b/lib/features/habib_wallet/habib_wallet_view_model.dart index a99c8351..3a10ad2d 100644 --- a/lib/features/habib_wallet/habib_wallet_view_model.dart +++ b/lib/features/habib_wallet/habib_wallet_view_model.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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/features/habib_wallet/habib_wallet_repo.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/models/lakum_inquiry_information_response_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/models/patient_advance_balance_response_model.dart'; @@ -49,6 +50,52 @@ class HabibWalletViewModel extends ChangeNotifier { bool isLakumAccountInfoLoading = false; late LakumInquiryInformationResponseModel lakumAccountInfo; + // Get all flattened transactions sorted by date descending + List getAllLakumTransactions() { + if (lakumAccountInfo.gainedPointsAmountPerYear == null) { + return []; + } + + List allTransactions = []; + + // Flatten the nested structure + for (var yearData in lakumAccountInfo.gainedPointsAmountPerYear!) { + if (yearData.pointsAmountPerMonth != null) { + for (var monthData in yearData.pointsAmountPerMonth!) { + if (monthData.pointsAmountPerday != null) { + for (var dayData in monthData.pointsAmountPerday!) { + if (dayData.pointsDetails != null) { + allTransactions.addAll(dayData.pointsDetails!); + } + } + } + } + } + } + + // Sort by transaction date DESCENDING (latest/most recent transactions first, older transactions last) + allTransactions.sort((a, b) { + // Null dates go to the end + if (a.transactionDate == null && b.transactionDate == null) return 0; + if (a.transactionDate == null) return 1; // a goes after b + if (b.transactionDate == null) return -1; // a goes before b + + try { + DateTime dateA = DateUtil.convertStringToDate(a.transactionDate!); + DateTime dateB = DateUtil.convertStringToDate(b.transactionDate!); + + // Compare dateB to dateA (not dateA to dateB) for DESCENDING order + // This puts newer dates (larger values) at the top of the list + return dateB.compareTo(dateA); + } catch (e) { + // If date parsing fails, maintain current order + return 0; + } + }); + + return allTransactions; + } + // Clear amount error void clearAmountError() { _amountError = null; diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index ad44d33f..475368f0 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -347,8 +347,11 @@ class PrescriptionsViewModel extends ChangeNotifier { result.fold( (failure) async { isPrescriptionsDeliveryOrdersLoading = false; + prescriptionsOrderList.clear(); notifyListeners(); - onError!(failure.message); + if(onError != null) { + onError(failure.message); + } }, (apiResponse) { if (apiResponse.messageStatus == 2) { diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 0e694d29..839be537 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1840,5 +1840,6 @@ abstract class LocaleKeys { static const submitEReferral = 'submitEReferral'; static const redeem = 'redeem'; static const points = 'points'; + static const transactions = 'transactions'; } diff --git a/lib/presentation/habib_wallet/habib_wallet_page.dart b/lib/presentation/habib_wallet/habib_wallet_page.dart index c21e7a62..42ad5984 100644 --- a/lib/presentation/habib_wallet/habib_wallet_page.dart +++ b/lib/presentation/habib_wallet/habib_wallet_page.dart @@ -33,6 +33,7 @@ class _HabibWalletState extends State { scheduleMicrotask(() async { habibWalletVM.initHabibWalletProvider(); habibWalletVM.getPatientBalanceAmount(); + habibWalletVM.getLakumAccountInformation(); }); super.initState(); @@ -123,6 +124,7 @@ class _HabibWalletState extends State { .then((val) { habibWalletVM.initHabibWalletProvider(); habibWalletVM.getPatientBalanceAmount(); + habibWalletVM.getLakumAccountInformation(); }); }, ), diff --git a/lib/presentation/home/lakum_wallet_details.dart b/lib/presentation/home/lakum_wallet_details.dart new file mode 100644 index 00000000..ce3133ec --- /dev/null +++ b/lib/presentation/home/lakum_wallet_details.dart @@ -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(); + + return CollapsingListView( + title: LocaleKeys.lakumPoints.tr(context: context), + child: Consumer( + 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 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 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; +} diff --git a/lib/presentation/home/widgets/lakum_wallet_card.dart b/lib/presentation/home/widgets/lakum_wallet_card.dart index f8abbeae..cda3c064 100644 --- a/lib/presentation/home/widgets/lakum_wallet_card.dart +++ b/lib/presentation/home/widgets/lakum_wallet_card.dart @@ -12,6 +12,7 @@ import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_mode import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; +import 'package:hmg_patient_app_new/presentation/home/lakum_wallet_details.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; @@ -66,7 +67,7 @@ class LakumWalletCard extends StatelessWidget { // mainAxisAlignment: MainAxisAlignment.spaceBetween, // children: [ // LocaleKeys.habibWallet.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), - "Lakum Wallet".toText16(isBold: true, letterSpacing: -0.2), + LocaleKeys.lakumPoints.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), // Container( // height: 40.h, // width: 40.h, @@ -121,14 +122,14 @@ class LakumWalletCard extends StatelessWidget { child: CustomButton( height: 40.h, icon: AppAssets.redeem_icon, - iconColor: AppColors.successColor, + iconColor: AppColors.habibPharmacyColor, iconSize: 24.h, - backgroundColor: AppColors.successColor.withAlpha(15), - textColor: AppColors.successColor, + backgroundColor: AppColors.habibPharmacyColor.withAlpha(15), + textColor: AppColors.habibPharmacyColor, text: LocaleKeys.redeem.tr(context: context), borderWidth: 0.w, fontWeight: FontWeight.w600, - borderColor: AppColors.successColor.withAlpha(15), + borderColor: AppColors.habibPharmacyColor.withAlpha(15), padding: EdgeInsets.fromLTRB(4, 0, 12, 0), fontSize: 14.f, onPressed: () { @@ -164,7 +165,7 @@ class LakumWalletCard extends StatelessWidget { ).onPress(() { Navigator.of(context).push( CustomPageRoute( - page: HabibWalletPage(), + page: LakumWalletDetails(), ), ); }), diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 1e39eac8..1ecbcb61 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -40,6 +40,7 @@ class AppColors { static Color get lightGreenColor => isDarkMode ? dark.lightGreenColor : const Color(0xFF0ccedde); static Color get ratingColorYellow => isDarkMode ? dark.ratingColorYellow : const Color(0xFFFFAF15); static Color get spacerLineColor => isDarkMode ? dark.spacerLineColor : const Color(0x2E30391A); + static Color get habibPharmacyColor => const Color(0xFF2d9137); // ── Doctor Rating Colors ────────────────────────────────────────────────── static const Color ratingStarIconColor = Color(0xFFFFA726); // Orange-amber for star icons