@ -2,14 +2,19 @@ import 'dart:io';
import ' package:easy_localization/easy_localization.dart ' ;
import ' package:flutter/material.dart ' ;
import ' package:flutter_staggered_animations/flutter_staggered_animations.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/route_extensions.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/refund_request/models/resp_models/refundable_invoices_response_model.dart ' ;
import ' package:hmg_patient_app_new/features/refund_request/refund_request_view_model.dart ' ;
import ' package:hmg_patient_app_new/generated/locale_keys.g.dart ' ;
import ' package:hmg_patient_app_new/presentation/habib_wallet/widgets/select_hospital_bottom_sheet.dart ' ;
import ' package:hmg_patient_app_new/presentation/refund_request/refund_history_screen.dart ' ;
@ -20,7 +25,9 @@ import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import ' package:hmg_patient_app_new/widgets/image_picker.dart ' ;
import ' package:hmg_patient_app_new/widgets/input_widget.dart ' ;
import ' package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart ' ;
import ' package:hmg_patient_app_new/widgets/radio_list_tile_widget.dart ' ;
import ' package:hmg_patient_app_new/widgets/routes/custom_page_route.dart ' ;
import ' package:hmg_patient_app_new/widgets/stepper/stepper_widget.dart ' ;
import ' package:provider/provider.dart ' ;
class WithdrawRequestCreatePage extends StatefulWidget {
@ -45,6 +52,8 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
_appState = getIt . get < AppState > ( ) ;
_habibWalletViewModel = context . read < HabibWalletViewModel > ( ) ;
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) {
_habibWalletViewModel . currentWithdrawStep = 0 ;
_habibWalletViewModel . selectedAdvance = null ;
_habibWalletViewModel . getProjectsList ( ) ;
} ) ;
}
@ -59,12 +68,262 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
super . dispose ( ) ;
}
bool get _isFormValid {
final vm = _habibWalletViewModel ;
return vm . selectedHospital ! = null & &
! vm . isAdvancesLoading & &
_bankDetailsController . text . trim ( ) . isNotEmpty & &
_descriptionController . text . trim ( ) . isNotEmpty ;
/ / ─ ─ Stepper ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
Widget _buildStepper ( HabibWalletViewModel vm ) {
return Padding (
padding: EdgeInsets . symmetric ( horizontal: 24. w ) ,
child: LayoutBuilder (
builder: ( context , constraints ) {
final itemWidth = ( constraints . maxWidth - 2 * 8. h ) / 2 ;
return Row (
children: List . generate ( 2 , ( index ) {
final isActive = index < = vm . currentWithdrawStep ;
final isThumb = index = = vm . currentWithdrawStep ;
return StepperWidget (
itemWidth ,
isActive ? AppColors . primaryRedColor : AppColors . greyColor ,
isThumb ,
6. h ,
) ;
} ) ,
) ;
} ,
) ,
) ;
}
/ / ─ ─ Step 1 — Select Advance ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
Widget _buildStep1Content ( HabibWalletViewModel vm ) {
return Consumer < HabibWalletViewModel > (
builder: ( context , walletVM , _ ) {
return Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
LocaleKeys . selectAdvance . tr ( context: context ) . toText20 ( isBold: true ) ,
SizedBox ( height: 24. h ) ,
if ( vm . isAdvancesLoading )
ListView . separated (
padding: EdgeInsets . zero ,
shrinkWrap: true ,
physics: const NeverScrollableScrollPhysics ( ) ,
separatorBuilder: ( context , index ) = > SizedBox ( height: 16. h ) ,
itemCount: 4 ,
itemBuilder: ( _ , index ) = > Container (
height: 80. h ,
decoration: RoundedRectangleBorder ( ) . toSmoothCornerDecoration (
color: AppColors . whiteColor ,
borderRadius: 16. h ,
) ,
) . toShimmer2 ( isShow: true , radius: 16. r ) ,
)
else if ( vm . refundableAdvancesList . isEmpty )
Center (
child: Padding (
padding: EdgeInsets . symmetric ( vertical: 24. h ) ,
child: LocaleKeys . noAdvancesAvailable . tr ( ) . toText14 ( color: AppColors . greyTextColor ) ,
) ,
)
else
AnimationLimiter (
child: Container (
decoration: RoundedRectangleBorder ( ) . toSmoothCornerDecoration (
color: AppColors . whiteColor ,
borderRadius: 24. h ,
hasShadow: false ,
) ,
child: ListView . separated (
padding: EdgeInsets . zero ,
shrinkWrap: true ,
physics: const NeverScrollableScrollPhysics ( ) ,
separatorBuilder: ( context , index ) = > Divider (
color: AppColors . bottomNAVBorder ,
height: 1 ,
thickness: 1 ,
) . paddingSymmetrical ( 16. w , 0. w ) ,
itemCount: vm . refundableAdvancesList . length ,
itemBuilder: ( context , index ) {
final advance = vm . refundableAdvancesList [ index ] ;
return _buildAdvanceCard ( advance , index ) ;
} ,
) ,
) ,
) ,
] ,
) ;
} ,
) ;
}
Widget _buildAdvanceCard ( RefundableAdvancesItem advance , int index ) {
return AnimationConfiguration . staggeredList (
position: index ,
duration: const Duration ( milliseconds: 400 ) ,
child: SlideAnimation (
verticalOffset: 50.0 ,
child: FadeInAnimation (
child: Material (
color: Colors . transparent ,
child: InkWell (
onTap: ( ) = > _habibWalletViewModel . setSelectedAdvance ( advance ) ,
child: Padding (
padding: EdgeInsets . all ( 16. h ) ,
child: Row (
crossAxisAlignment: CrossAxisAlignment . center ,
children: [
SizedBox (
width: 24. w ,
height: 24. w ,
child: Radio < int > (
value: advance . advanceNo ? ? 0 ,
groupValue: _habibWalletViewModel . selectedAdvance ? . advanceNo ? ? - 1 ,
onChanged: ( _ ) = > _habibWalletViewModel . setSelectedAdvance ( advance ) ,
activeColor: AppColors . primaryRedColor ,
materialTapTargetSize: MaterialTapTargetSize . shrinkWrap ,
visualDensity: VisualDensity . compact ,
) ,
) ,
SizedBox ( width: 12. w ) ,
Expanded (
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
" ${ LocaleKeys . advanceNo . tr ( ) } : ${ advance . advanceNo ? ? 0 } " . toText16 ( isBold: true ) ,
SizedBox ( height: 4. h ) ,
_buildAdvanceDate ( advance . advanceDate ) ,
] ,
) ,
) ,
SizedBox ( width: 8. w ) ,
Utils . getPaymentAmountWithSymbol (
( advance . balanceAmount ? ? 0.0 ) . toStringAsFixed ( 2 ) . toText20 (
isBold: true ,
isEnglishOnly: true ,
color: AppColors . textColor ,
) ,
AppColors . textColor ,
10. f ,
isSaudiCurrency: true ,
) ,
] ,
) ,
) ,
) ,
) ,
) ,
) ,
) ;
}
Widget _buildAdvanceDate ( String ? dateString ) {
if ( dateString = = null | | dateString . isEmpty ) {
return const SizedBox . shrink ( ) ;
}
try {
final date = DateUtil . convertStringToDate ( dateString ) ;
final formattedDate = DateUtil . formatDateToDate ( date , false ) ;
return formattedDate . toText12 ( color: AppColors . greyTextColor ) ;
} catch ( e ) {
return const SizedBox . shrink ( ) ;
}
}
/ / ─ ─ Step 2 — Refund Method Selection ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
Widget _buildRefundMethodSelection ( HabibWalletViewModel vm ) {
final paymentOptions = [
RefundTabType . invoices , / / Bank Transfer
RefundTabType . cash , / / Cash
] ;
return Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
LocaleKeys . selectRefundMethod . tr ( context: context ) . toText20 ( isBold: true ) ,
SizedBox ( height: 24. h ) ,
Container (
decoration: RoundedRectangleBorder ( ) . toSmoothCornerDecoration (
color: AppColors . whiteColor ,
borderRadius: 24. h ,
hasShadow: false ,
) ,
child: ListView . builder (
padding: EdgeInsets . zero ,
itemCount: paymentOptions . length ,
shrinkWrap: true ,
physics: const NeverScrollableScrollPhysics ( ) ,
itemBuilder: ( context , index ) {
final type = paymentOptions [ index ] ;
final isCashOption = type = = RefundTabType . cash ;
final isCashSelected = vm . selectedWithdrawMethod = = RefundTabType . cash ;
return Column (
children: [
RadioListTileWidget < RefundTabType > (
value: type ,
groupValue: vm . selectedWithdrawMethod ,
title: type . toDisplayString ( ) ,
onChanged: ( value ) {
if ( value ! = null ) vm . setSelectedWithdrawMethod ( value ) ;
} ,
) ,
/ / Show cash instructions inside cash tile when selected
if ( isCashOption & & isCashSelected ) . . . [
Padding (
padding: EdgeInsets . fromLTRB ( 16. w , 0 , 16. w , 16. h ) ,
child: Row (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Utils . buildSvgWithAssets (
icon: AppAssets . alertSquare ,
height: 24. h ,
width: 24. h ,
iconColor: AppColors . textColor ,
) ,
SizedBox ( width: 12. w ) ,
Expanded (
child: LocaleKeys . cashRefundApprovalInstructions . tr ( context: context ) . toText12 (
color: AppColors . greyInfoTextColor ,
) ,
) ,
] ,
) ,
) ,
] ,
if ( index ! = paymentOptions . length - 1 )
Divider ( color: AppColors . bottomNAVBorder , height: 1 , thickness: 1 ) . paddingSymmetrical ( 16. w , 0. w ) ,
] ,
) ;
} ,
) ,
) ,
if ( vm . selectedWithdrawMethod = = RefundTabType . cash & & vm . isCashWithdrawAmountExceeded ) . . . [
SizedBox ( height: 16. h ) ,
Container (
padding: EdgeInsets . all ( 16. h ) ,
decoration: BoxDecoration (
color: AppColors . warningColorYellow . withValues ( alpha: 0.1 ) ,
borderRadius: BorderRadius . circular ( 12. r ) ,
border: Border . all ( color: AppColors . warningColorYellow , width: 1 ) ,
) ,
child: Row (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Icon ( Icons . info_outline , color: AppColors . warningColorYellow , size: 20. h ) ,
SizedBox ( width: 12. w ) ,
Expanded (
child: LocaleKeys . cashRefundLimitExceeded . tr ( context: context ) . toText14 (
color: AppColors . textColor ,
) ,
) ,
] ,
) ,
) ,
] ,
] ,
) ;
}
/ / ─ ─ Bottom bar ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
@ -72,10 +331,23 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
Widget _buildBottomBar ( ) {
return Consumer < HabibWalletViewModel > (
builder: ( context , vm , _ ) {
final isValid = vm . selectedHospital ! = null & &
! vm . isAdvancesLoading & &
_bankDetailsController . text . trim ( ) . isNotEmpty & &
_descriptionController . text . trim ( ) . isNotEmpty ;
final isStep2 = vm . currentWithdrawStep = = 1 ;
/ / Step 1 validation: advance selected
final isStep1Valid = vm . selectedAdvance ! = null ;
/ / Step 2 validation:
/ / - Refund method must be selected
/ / - If Bank Transfer selected , IBAN and description required
/ / - If Cash selected , amount must be < = 5000 SAR
final isBankTransfer = vm . selectedWithdrawMethod = = RefundTabType . invoices ;
final isCash = vm . selectedWithdrawMethod = = RefundTabType . cash ;
final isStep2Valid = vm . selectedWithdrawMethod ! = null & &
( isCash
? ! vm . isCashWithdrawAmountExceeded / / Cash: check amount limit
: ( isBankTransfer & &
_bankDetailsController . text . trim ( ) . isNotEmpty & &
_descriptionController . text . trim ( ) . isNotEmpty ) ) ; / / Bank: check fields
return Container (
decoration: RoundedRectangleBorder ( ) . toSmoothCornerDecoration (
@ -86,15 +358,47 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
if ( vm . selectedAdvance ! = null ) . . . [
Padding (
padding: EdgeInsets . fromLTRB ( 24. w , 16. h , 24. w , 0 ) ,
child: Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
children: [
LocaleKeys . totalAmount . tr ( ) . toText18 ( color: AppColors . textColor , isBold: true ) ,
Utils . getPaymentAmountWithSymbol (
vm . selectedAdvanceBalance . toStringAsFixed ( 2 ) . toText24 (
isBold: true ,
isEnglishOnly: true ,
color: AppColors . textColor ,
) ,
AppColors . textColorLight ,
14 ,
isSaudiCurrency: true ,
) ,
] ,
) ,
) ,
] ,
CustomButton (
text: LocaleKeys . submitRequest . tr ( context: context ) ,
isDisabled: ! isValid ,
icon: isStep2 ? AppAssets . refundIcon : null ,
text: isStep2 ? LocaleKeys . submit . tr ( context: context ) : LocaleKeys . next . tr ( context: context ) ,
isDisabled: isStep2 ? ! isStep2Valid : ! isStep1Valid ,
onPressed: ( ) {
final isFormValid = vm . validateWithdrawForm (
iban: _bankDetailsController . text ,
description: _descriptionController . text ,
) ;
if ( isFormValid ) {
if ( ! isStep2 ) {
/ / Move to step 2
if ( vm . selectedAdvance ! = null ) {
vm . nextWithdrawStep ( ) ;
}
} else {
/ / Submit - only validate bank details if Bank Transfer is selected
final isBankTransfer = vm . selectedWithdrawMethod = = RefundTabType . invoices ;
if ( isBankTransfer ) {
final isFormValid = vm . validateWithdrawForm (
iban: _bankDetailsController . text ,
description: _descriptionController . text ,
) ;
if ( ! isFormValid ) return ;
}
_handleWalletWithdrawalSubmit ( vm ) ;
}
} ,
@ -108,6 +412,8 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
Widget _buildProjectSelectionAndBalance ( HabibWalletViewModel habibWalletViewModel ) {
final isArabic = _appState . isArabic ( ) ;
final showAmount = habibWalletViewModel . currentWithdrawStep = = 1 & & habibWalletViewModel . selectedAdvance ! = null ;
return Container (
padding: EdgeInsets . all ( 16. h ) ,
decoration: RoundedRectangleBorder ( ) . toSmoothCornerDecoration ( color: AppColors . whiteColor , borderRadius: 24. r ) ,
@ -137,45 +443,46 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
] ) ,
] ,
) ,
Utils . buildSvgWithAssets (
icon: AppAssets . arrow_down ,
width: 25. h ,
height: 25. h ,
) ,
if ( habibWalletViewModel . currentWithdrawStep = = 0 )
Utils . buildSvgWithAssets (
icon: AppAssets . arrow_down ,
width: 25. h ,
height: 25. h ,
) ,
] ,
) . onPress ( ( ) {
showCommonBottomSheetWithoutHeight (
context ,
title: LocaleKeys . selectHospital . tr ( context: context ) ,
isDismissible: false ,
child: SelectHospitalBottomSheet (
onHospitalSelected: ( hospital ) {
habibWalletViewModel . setSelectedHospital ( hospital ) ;
habibWalletViewModel . fetchAdvancesForHospital ( ) ;
} ,
) ,
callBackFunc: ( ) { } ,
) ;
} ) ,
/ / Only show divider + amount once hospital is selected
if ( habibWalletViewModel . selectedHospital ! = null ) . . . [
) . onPress (
habibWalletViewModel . currentWithdrawStep = = 0
? ( ) {
showCommonBottomSheetWithoutHeight (
context ,
title: LocaleKeys . selectHospital . tr ( context: context ) ,
isDismissible: false ,
child: SelectHospitalBottomSheet (
onHospitalSelected: ( hospital ) {
habibWalletViewModel . setSelectedHospital ( hospital ) ;
habibWalletViewModel . fetchAdvancesForHospital ( ) ;
if ( hospital . iD ! = null ) {
final projectId = hospital . iD is int ? hospital . iD as int : int . tryParse ( hospital . iD . toString ( ) ) ;
if ( projectId ! = null ) {
habibWalletViewModel . getThresholdAmount ( projectId: projectId ) ;
}
}
} ,
) ,
callBackFunc: ( ) { } ,
) ;
}
: ( ) { } , / / Empty callback for step 2
) ,
if ( showAmount ) . . . [
Divider ( color: AppColors . bottomNAVBorder , thickness: 1 ) . paddingOnly ( top: 16. h ) ,
habibWalletViewModel . isAdvancesLoading
? Container (
height: 60. h ,
/ / width: 160. w ,
decoration: BoxDecoration (
color: AppColors . whiteColor ,
borderRadius: BorderRadius . circular ( 12. r ) ,
) ,
) . toShimmer2 ( isShow: true , radius: 12. r )
: Utils . getPaymentAmountWithSymbol (
habibWalletViewModel . totalAdvancesBalance . toStringAsFixed ( 2 ) . toText44 ( isBold: true ) ,
AppColors . blackColor ,
18. w ,
isSaudiCurrency: true ,
isExpanded: false ,
) ,
Utils . getPaymentAmountWithSymbol (
habibWalletViewModel . selectedAdvanceBalance . toStringAsFixed ( 2 ) . toText44 ( isBold: true ) ,
AppColors . blackColor ,
18. w ,
isSaudiCurrency: true ,
isExpanded: false ,
) ,
] ,
] ,
) ,
@ -254,42 +561,157 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
) ;
}
Widget _buildImagePickerDropDown ( ) {
return Container (
padding: EdgeInsets . all ( 16. h ) ,
decoration: RoundedRectangleBorder ( ) . toSmoothCornerDecoration ( color: AppColors . whiteColor , borderRadius: 24. r ) ,
child: Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
children: [
Row (
children: [
Utils . buildSvgWithAssets (
icon: AppAssets . selectImageIcon ,
width: 40. h ,
height: 40. h ,
applyThemeColor: false ,
/ / ─ ─ Image Upload Section ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
Widget _buildImageUploadSection ( HabibWalletViewModel vm ) {
return Consumer < HabibWalletViewModel > (
builder: ( context , viewModel , _ ) {
final hasError = viewModel . withdrawAttachmentError ! = null ;
return Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
/ / Upload button
InkWell (
onTap: ( ) {
if ( viewModel . withdrawAttachmentList . length > = 4 ) {
viewModel . setWithdrawAttachmentError ( LocaleKeys . maximumFilesReached . tr ( context: context ) ) ;
return ;
}
ImageOptions . showImageOptionsNew (
context ,
true ,
( String base64Image , File file ) async {
/ / Validate file size ( 2 MB max )
final fileSize = await file . length ( ) ;
if ( fileSize > HabibWalletViewModel . maxFileSizeBytes ) {
viewModel . setWithdrawAttachmentError ( LocaleKeys . fileSizeTooLarge . tr ( context: context ) ) ;
return ;
}
/ / Check max files limit
if ( viewModel . withdrawAttachmentList . length > = HabibWalletViewModel . maxAttachments ) {
viewModel . setWithdrawAttachmentError ( LocaleKeys . maximumFilesReached . tr ( context: context ) ) ;
return ;
}
/ / Add attachment
viewModel . addWithdrawAttachment ( base64Image ) ;
} ,
title: LocaleKeys . selectAttachment . tr ( context: context ) ,
allowMultiple: true , / / Enable multiple selection
) ;
} ,
child: Container (
padding: EdgeInsets . all ( 16. h ) ,
decoration: BoxDecoration (
color: AppColors . whiteColor ,
borderRadius: BorderRadius . circular ( 24. r ) ,
border: hasError ? Border . all ( color: AppColors . primaryRedBorderColor , width: 1 ) : null ,
) ,
child: Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
crossAxisAlignment: CrossAxisAlignment . center ,
children: [
Expanded (
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Row (
children: [
Container (
height: 40. h ,
width: 40. h ,
padding: EdgeInsets . all ( 8. h ) ,
decoration: RoundedRectangleBorder ( ) . toSmoothCornerDecoration (
borderRadius: 12. r ,
color: AppColors . greyColor ,
) ,
child: Utils . buildSvgWithAssets ( icon: AppAssets . selectImageIcon , applyThemeColor: false ) ,
) ,
SizedBox ( width: 12. w ) ,
Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
LocaleKeys . uploadImage . tr ( context: context ) . toText12 (
color: AppColors . inputLabelTextColor ,
isBold: true ,
) ,
LocaleKeys . selectImage . tr ( context: context ) . toText14 (
color: AppColors . inputLabelTextColor ,
isBold: true ,
) ,
] ,
) ,
] ,
) ,
if ( hasError ) . . . [
SizedBox ( height: 8. h ) ,
Row (
mainAxisAlignment: MainAxisAlignment . end ,
children: [
Text (
viewModel . withdrawAttachmentError ! ,
style: TextStyle (
color: AppColors . primaryRedColor ,
fontSize: 12. f ,
fontWeight: FontWeight . w600 ,
) ,
) ,
] ,
) ,
] ,
] ,
) ,
) ,
Utils . buildSvgWithAssets ( icon: AppAssets . arrow_down , width: 25. h , height: 25. h ) ,
] ,
) ,
) ,
) ,
/ / Selected images list
if ( viewModel . withdrawAttachmentList . isNotEmpty ) . . . [
SizedBox ( height: 16. h ) ,
Container (
decoration: RoundedRectangleBorder ( ) . toSmoothCornerDecoration (
color: AppColors . whiteColor ,
borderRadius: 24. r ,
hasShadow: false ,
) ,
child: ListView . separated (
padding: EdgeInsets . all ( 16. h ) ,
shrinkWrap: true ,
physics: const NeverScrollableScrollPhysics ( ) ,
itemCount: viewModel . withdrawAttachmentList . length ,
separatorBuilder: ( context , index ) = > SizedBox ( height: 12. h ) ,
itemBuilder: ( BuildContext context , int index ) {
return Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
crossAxisAlignment: CrossAxisAlignment . center ,
children: [
Row (
crossAxisAlignment: CrossAxisAlignment . center ,
children: [
Icon (
Icons . attach_file ,
color: Color ( 0xff2B353E ) ,
size: 20. h ,
) ,
SizedBox ( width: 8. w ) ,
" ${ LocaleKeys . image . tr ( context: context ) } ${ index + 1 } " . toText14 ( ) ,
] ,
) ,
Utils . buildSvgWithAssets ( icon: AppAssets . cancel_circle_icon , width: 20. h , height: 20. h ) . onPress ( ( ) {
viewModel . removeWithdrawAttachment ( index ) ;
} ) ,
] ,
) ;
} ,
) ,
) ,
SizedBox ( width: 8. h ) ,
Column ( crossAxisAlignment: CrossAxisAlignment . start , children: [
LocaleKeys . uploadImage . tr ( context: context ) . toText12 ( color: AppColors . greyTextColor , isBold: true ) ,
LocaleKeys . selectImage . tr ( context: context ) . toText14 ( color: AppColors . textColor , isBold: true ) ,
] ) ,
] ,
) ,
Utils . buildSvgWithAssets (
icon: AppAssets . arrow_down ,
width: 25. h ,
height: 25. h ,
) ,
] ,
) . onPress ( ( ) {
ImageOptions . showImageOptionsNew (
context ,
false ,
title: LocaleKeys . selectOptions . tr ( context: context ) ,
( String image , File file ) { } ,
] ,
) ;
} ) ,
} ,
) ;
}
@ -310,6 +732,11 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
LoaderBottomSheet . showLoader ( ) ;
final user = _appState . getAuthenticatedUser ( ) ;
final hospital = vm . selectedHospital ;
/ / Determine refund destination: ' 2 ' = Bank Transfer , ' 3 ' = Cash
final isCash = vm . selectedWithdrawMethod = = RefundTabType . cash ;
final refundDestination = isCash ? ' 3 ' : ' 2 ' ;
await vm . submitWalletWithdrawalRequest (
iban: _bankDetailsController . text . trim ( ) ,
remarks: _descriptionController . text . trim ( ) ,
@ -319,9 +746,10 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
patientNationalId: user ? . patientIdentificationNo ? . toString ( ) ? ? ' ' ,
projectId: hospital ? . mainProjectID ? ? 0 ,
projectName: hospital ? . name ? ? ' ' ,
refundDestination: refundDestination ,
onSuccess: ( response ) {
LoaderBottomSheet . hideLoader ( ) ;
_showWithdrawalSuccessBottomSheet ( response . refNo ? ? ' ' );
_showWithdrawalSuccessBottomSheet ( response . refNo ? ? ' ' , refundDestination );
} ,
onError: ( err ) {
LoaderBottomSheet . hideLoader ( ) ;
@ -340,7 +768,10 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
) ;
}
void _showWithdrawalSuccessBottomSheet ( String refNo ) {
void _showWithdrawalSuccessBottomSheet ( String refNo , String refundDestination ) {
/ / refundDestination: ' 2 ' = Bank Transfer , ' 3 ' = Cash
final isCashRefund = refundDestination = = ' 3 ' ;
showCommonBottomSheetWithoutHeight (
context ,
child: Padding (
@ -359,7 +790,11 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
SizedBox ( width: 6. w ) ,
refNo . toText18 ( isBold: true , color: AppColors . textColor , isEnglishOnly: true ) ,
] ,
) . paddingSymmetrical ( 24. w , 0 ) ,
) . paddingSymmetrical ( 16. w , 0 ) ,
] ,
if ( isCashRefund ) . . . [
SizedBox ( height: 16. h ) ,
LocaleKeys . cashRefundApprovalInstructions . tr ( context: context ) . toText14 ( color: AppColors . textColor ) . paddingSymmetrical ( 16. w , 0 ) ,
] ,
SizedBox ( height: 24. h ) ,
Row (
@ -374,7 +809,7 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
Navigator . of ( context ) . push (
CustomPageRoute (
page: RefundHistoryScreen (
refundType: 1 , / / 2 for wallet , 1 for invoice
refundType: 2 , / / 2 for wallet , 1 for invoice
) ,
) ,
) ;
@ -407,6 +842,13 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
Expanded (
child: CollapsingListView (
title: LocaleKeys . requestRefund . tr ( context: context ) ,
leadingCallback: ( ) {
if ( habibWalletViewModel . currentWithdrawStep > 0 ) {
habibWalletViewModel . previousWithdrawStep ( ) ;
} else {
context . pop ( ) ;
}
} ,
requests: ( ) = > Navigator . of ( context ) . push (
CustomPageRoute (
page: RefundHistoryScreen (
@ -415,17 +857,66 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
) ,
) ,
child: SingleChildScrollView (
padding: EdgeInsets . only ( bottom: 32. h , left: 24. w , right: 24. w ),
padding: EdgeInsets . only ( bottom: 32. h ),
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
_buildProjectSelectionAndBalance ( habibWalletViewModel ) ,
/ / Hospital selection card - always visible
Padding (
padding: EdgeInsets . symmetric ( horizontal: 24. w ) ,
child: _buildProjectSelectionAndBalance ( habibWalletViewModel ) ,
) ,
/ / Stepper - show only after hospital selected
if ( habibWalletViewModel . selectedHospital ! = null ) . . . [
SizedBox ( height: 16. h ) ,
_buildBankInputDetails ( ) ,
/ / SizedBox ( height: 16. h ) ,
/ / _buildImagePickerDropDown ( ) ,
_buildStepper ( habibWalletViewModel ) ,
SizedBox ( height: 16. h ) ,
] ,
/ / Step content - show only after hospital selected
if ( habibWalletViewModel . selectedHospital ! = null )
AnimatedSwitcher (
duration: const Duration ( milliseconds: 350 ) ,
transitionBuilder: ( child , animation ) = > FadeTransition (
opacity: animation ,
child: SlideTransition (
position: Tween < Offset > (
begin: const Offset ( 0.05 , 0 ) ,
end: Offset . zero ,
) . animate ( animation ) ,
child: child ,
) ,
) ,
child: habibWalletViewModel . currentWithdrawStep = = 0
? KeyedSubtree (
key: const ValueKey ( ' step1 ' ) ,
child: Padding (
padding: EdgeInsets . symmetric ( horizontal: 24. w ) ,
child: _buildStep1Content ( habibWalletViewModel ) ,
) ,
)
: KeyedSubtree (
key: const ValueKey ( ' step2 ' ) ,
child: Padding (
padding: EdgeInsets . symmetric ( horizontal: 24. w ) ,
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
_buildRefundMethodSelection ( habibWalletViewModel ) ,
if ( habibWalletViewModel . selectedWithdrawMethod = = RefundTabType . invoices ) . . . [
SizedBox ( height: 16. h ) ,
_buildBankInputDetails ( ) ,
] ,
if ( habibWalletViewModel . selectedWithdrawMethod ! = null ) . . . [
SizedBox ( height: 16. h ) ,
_buildImageUploadSection ( habibWalletViewModel ) ,
] ,
] ,
) ,
) ,
) ,
) ,
] ,
) ,
) ,
@ -439,7 +930,3 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
) ;
}
}
/ / I / flutter ( 13750 ) : uri: https: / / uat . hmgwebservices . com / Services / COCWS . svc / REST / SubmitRefund
/ / [ log ] body: { " ProjectID " : 15 , " Refund_RefundType " : " 2 " , " Refund_Project " : " مستشفى الريان " , " Refund_ProjectId " : 15 , " Refund_PatientId " : " 4770714 " , " PatientMobileNumber " : " 0504278212 " , " Refund_PatientName " : " FAIZ UR REHMAN REHMAN " , " Refund_NationalId " : " 2530976584 " , " Refund_DoctorId " : 0 , " Refund_DoctorName " : " " , " Refund_RefundDestination " : " 2 " , " Refund_IBAN " : " SA0380000000608010167519 " , " Refund_Remarks " : " teskchjvs " , " Refund_TotalRefundAmount " : 1714.75 , " Refund_AdvancesList " : [ { " AdvanceNo " : " 2026000444 " , " PaidAmount " : 362.25 , " BalanceAmount " : 362.25 , " AdvanceDate " : " /Date(1775682000000+0300)/ " } , { " AdvanceNo " : " 2026000392 " , " PaidAmount " : 362.25 , " BalanceAmount " : 362.25 , " AdvanceDate " : " /Date(1774645200000+0300)/ " } , { " AdvanceNo " : " 2026000066 " , " PaidAmount " : 362.25 , " BalanceAmount " : 362.25 , " AdvanceDate " : " /Date(1770152400000+0300)/ " } , { " AdvanceNo " : " 2026000047 " , " PaidAmount " : 30.0 , " BalanceAmount " : 30.0 , " AdvanceDate " : " /Date(1769547600000+0300)/ " } , { " AdvanceNo " : " 2025000751 " , " PaidAmount " : 362.25 , " BalanceAmount " : 362.25 , " AdvanceDate " : " /Date(1765746000000+0300)/ " } , { " AdvanceNo " : " 2025000414 " , " PaidAmount " : 115.0 , " BalanceAmount " : 115.0 , " AdvanceDate " : " /Date(1747083600000+0300)/ " } , { " AdvanceNo " : " 2025000210 " , " PaidAmount " : 120.75 , " BalanceAmount " : 120.75 , " AdvanceDate " : " /Date(1741726800000+0300)/ " } ] , " PatientType " : 1 , " PatientTypeID " : 1 , " TokenID " : " @dm!n " , " PatientID " : 4770714 , " PatientOutSA " : 0 , " SessionID " : " eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNi0wNS0xOFQwNzo1MzoyMS4wMjQ5MDM2WiIsIlBhdGllbnRJRCI6IjQ3NzIxNjMiLCJQYXRpZW50TWVyZ2VkSURzIjoiNDc3MjE2MyIsIlBhdGllbnRPdXRTQSI6ZmFsc2UsIlNoYXJlZElkcyI6IiJ9LCJleHAiOjE3NzkxMzgwMDB9.VHd_kCya33GfK-DywTHwSU1PMewbQB7UhQD1zXS7X5w " , " VersionID " : " 21.2 " , " Channel " : " 3 " , " IPAdress " : " 10.20.10.20 " , " generalid " : " Cs2020@2016 $ 2958 " , " LanguageID " : " 1 " , " Latitude " : " 0.0 " , " Longitude " : " 0.0 " , " DeviceTypeID " : " 2 " , " IsNewFlutterApp " : true }
/ / [ log ] response: { " Date " : null , " LanguageID " : 0 , " ServiceName " : 0 , " Time " : null , " AndroidLink " : null , " AuthenticationTokenID " : null , " Data " : null , " Dataw " : false , " DietType " : 0 , " DietTypeID " : 0 , " ErrorCode " : null , " ErrorEndUserMessage " : " Internal server error occurred. " , " ErrorEndUserMessageN " : null , " ErrorMessage " : null , " ErrorStatusCode " : 0 , " ErrorType " : 0 , " FoodCategory " : 0 , " IOSLink " : null , " IsAuthenticated " : true , " MealOrderStatus " : 0 , " MealType " : 0 , " MessageStatus " : 2 , " NumberOfResultRecords " : 0 , " PatientBlodType " : null , " RequestId " : null , " SuccessMsg " : null , " SuccessMsgN " : null , " VidaUpdatedResponse " : null , " ENSAT_GetPatientRequestListResponse " : null , " ENSAT_Patient_External_Response " : null , " ETQAN_CreateIncident_Response " : null , " ETQAN_GetEmployeeOVRRequests_Response " : null , " ETQAN_GetIncidentRequest_Response " : null , " ETQAN_GetProjects_Response " : null , " ITGRequest " : null , " Itg_FormAttachmentsList " : null , " ListCOCItems " : null , " Message " : " Refunds API returned status 500. Body: { \" message \" : \" Internal server error occurred. \" } " , " Mohemm_ENSAT_GetItgToken_Response " : null , " Mohemm_ITG_DepartmentSectionsList " : null , " Mohemm_ITG_Pending_Task_ResponseItem " : null , " Mohemm_ITG_ProjectDepartmentsList " : null , " Mohemm_ITG_ResponseItem " : null , " Mohemm_ITG_SectionTopicsList " : null , " Mohemm_ITG_TicketDetailsList " : null , " Mohemm_ITG_TicketTransactionsList " : null , " Mohemm_ITG_TicketsByEmployeeList " : null , " Mohemm_Itg_ProjectsList " : null , " Mohemm_Itg_TicketTypesList " : null , " ReferenceNumber " : null , " Refund_InvoicesList " : null , " Refund_PatientIdDisplay " : null , " Refund_RefNo " : null , " Refund_RefundTypeDisplay " : null , " Refund_StatusItem " : null , " Refund_TotalInvoices " : 0 , " Refund_VerifyResult " : null , " RequestType " : null , " TotalCount " : 0 , " statuseCode " : 500 }