5 Commits

Author SHA1 Message Date
1c3a07bd66 Test APK with cheque #2 2025-12-31 13:52:37 +05:30
d44ee5590e Test APK with cheque 2025-12-31 13:45:19 +05:30
715162b713 Test Sim Binding APK 2025-12-11 12:18:25 +05:30
8149ef2a5b Sim Binding Done #1 2025-12-10 11:50:02 +05:30
1a2dea611b SMS integrated with new ui 2025-12-09 18:11:46 +05:30
46 changed files with 693 additions and 7068 deletions

View File

@@ -2,6 +2,8 @@
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<application
android:label="kmobile"
android:name="${applicationName}"

View File

@@ -8,6 +8,41 @@ class AuthService {
final Dio _dio;
AuthService(this._dio);
Future<void> simVerify(String uuid, String cifNo) async {
try {
final response = await _dio.post('/api/sim-details-verify', data: {
'uuid': uuid,
'cifNo': cifNo,
});
if (response.statusCode == 200) {
final String message = response.data.toString().toUpperCase();
if (message.contains("VERIFIED")) {
return; // Success
} else {
throw AuthException(message); // Throw message received
}
} else {
throw AuthException('Verification Failed');
}
} on DioException catch (e) {
if (kDebugMode) {
print(e.toString());
}
if (e.response?.statusCode == 401) {
throw AuthException(
e.response?.data['error'] ?? 'SOMETHING WENT WRONG');
}
throw NetworkException('Network error during verification');
} catch (e) {
throw UnexpectedException(
'Unexpected error during verification: ${e.toString()}');
}
}
Future<AuthToken> login(AuthCredentials credentials) async {
try {
final response = await _dio.post(

View File

@@ -126,26 +126,4 @@ class BeneficiaryService {
throw Exception('Unexpected error: ${e.toString()}');
}
}
Future<Response> updateLimit({
required String beneficiaryAccountNo,
required String newLimit,
}) async {
log('inside update limit of beneficiary service');
final response = await _dio.patch(
'/api/beneficiary/update-limit',
data: {
'beneficiaryAccountNo': beneficiaryAccountNo,
'newLimit': int.tryParse(newLimit),
},
options: Options(
sendTimeout: const Duration(seconds: 60),
receiveTimeout: const Duration(seconds: 60),
),
);
if (response.statusCode != 200) {
throw Exception("INTERNAL SERVER ERROR");
}
return response;
}
}

View File

@@ -105,7 +105,7 @@ class BranchService {
if (response.statusCode == 200) {
return Branch.listFromJson(response.data);
} else {
throw Exception("Failed to fetch");
throw Exception("Failed to fetch beneficiaries");
}
} catch (e) {
return [];

View File

@@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:dio/dio.dart';
class Cheque {
@@ -128,64 +130,9 @@ class ChequeService {
'tpin': tpin,
},
);
return response.toString();
}
Future revokeStop({
required String accountno,
required String removeFromChequeNo,
required String instrType,
String? removeToChequeNo,
String? removeIssueDate,
String? removeExpiryDate,
String? removeAmount,
String? removeComment,
required String tpin,
}) async {
final response = await _dio.post(
'/api/cheque/removeStop',
options: Options(
validateStatus: (int? status) => true,
receiveDataWhenStatusError: true,
),
data: {
'accountNumber': accountno,
'removeFromChequeNo': removeFromChequeNo,
'instrumentType': instrType,
'removeToChequeNo': removeToChequeNo,
'removeIssueDate': removeIssueDate,
'removeExpiryDate': removeExpiryDate,
'removeAmount': removeAmount,
'removeComment': removeComment,
'tpin': tpin,
},
);
return response.toString();
}
Future registerPPS({
required String cheque_no,
required String account_number,
String? issue_date,
String? amount,
String? payee_name,
required String tpin,
}) async {
final response = await _dio.post(
'/api/pps',
options: Options(
validateStatus: (int? status) => true,
receiveDataWhenStatusError: true,
),
data: {
'cheque_no': cheque_no,
'account_number': account_number,
'issue_date': issue_date,
'amount': amount,
'payee_name': payee_name,
'tpin': tpin,
},
);
if (response.statusCode != 200) {
throw Exception(jsonEncode(response.data));
}
return response.toString();
}
}

View File

@@ -1,175 +0,0 @@
import 'dart:developer';
import 'package:dio/dio.dart';
class DepositService{
final Dio _dio;
DepositService(this._dio);
Future<dynamic> fetchaccountdetails({
required String accountno,
}) async {
try {
final response = await _dio.post(
"/api/deposit/req/deposit",
data: {
'accountNo': accountno,
},
options: Options(
headers: {
"Content-Type": "application/json",
},
),
);
log("PMY Details Response: ${response.data}");
if (response.statusCode == 200) {
return response.data;
} else {
throw Exception("INTERNAL SERVER ERROR");
}
} catch (e) {
log("Error fetching Account details: $e");
return null;
}
}
Future createFdTd({
String? fromacctno,
String? cifNo,
String? idno,
String? customername,
String? nationality,
String? addressline1,
String? addressline2,
String? pincode,
String? product,
String? type,
String? customercategory,
String? termlocation,
String? currency,
String? acctsgmtcode,
String? interestpaymentmethod,
String? taxfilenumberindicator,
String? termlenght,
String? termbasis,
String? termvaluedeposited,
String? interestfrequency,
String? termdays,
String? termmonths,
String? termyears,
String? nominationRequired,
String? printNomineename,
required String tpin,
}) async {
final response = await _dio.post(
'/api/deposit/create/fd-td',
options: Options(
validateStatus: (int? status) => true,
receiveDataWhenStatusError: true,
),
data: {
"fromacctno": fromacctno,
"cifNo": cifNo,
"idno": idno,
"customername": customername,
"nationality": nationality,
"addressline1": addressline1,
"addressline2": addressline2,
"pincode": pincode,
"product": product,
"type": type,
"customercategory": customercategory,
"termlocation": termlocation,
"currency": currency,
"acctsgmtcode": acctsgmtcode,
"interestpaymentmethod": interestpaymentmethod,
"taxfilenumberindicator": taxfilenumberindicator,
"termlenght": termlenght,
"termbasis": termbasis,
"termvaluedeposited": termvaluedeposited,
"interestfrequency": interestfrequency,
"termdays": termdays,
"termmonths": termmonths,
"termyears": termyears,
"nominationRequired": nominationRequired,
"printNomineename": printNomineename,
'tpin': tpin,
},
);
return response.data;
}
Future createRd({
String? fromacctno,
String? cifNo,
String? idno,
String? customername,
String? nationality,
String? addressline1,
String? addressline2,
String? pincode,
String? product,
String? type,
String? customercategory,
String? termlocation,
String? currency,
String? acctsgmtcode,
String? interestpaymentmethod,
String? taxfilenumberindicator,
String? termlenght,
String? termbasis,
String? termvaluedeposited,
String? interestfrequency,
String? termdays,
String? termmonths,
String? termyears,
String? nominationRequired,
String? printNomineename,
String? rdinstallmentvalue,
String? monthlyRDInstallmentdueday,
String? rdInstlFreq,
required String tpin,
}) async {
final response = await _dio.post(
'/api/deposit/create/rd',
options: Options(
validateStatus: (int? status) => true,
receiveDataWhenStatusError: true,
),
data: {
"fromacctno": fromacctno,
"cifNo": cifNo,
"idno": idno,
"customername": customername,
"nationality": nationality,
"addressline1": addressline1,
"addressline2": addressline2,
"pincode": pincode,
"product": product,
"type": type,
"customercategory": customercategory,
"termlocation": termlocation,
"currency": currency,
"acctsgmtcode": acctsgmtcode,
"interestpaymentmethod": interestpaymentmethod,
"taxfilenumberindicator": taxfilenumberindicator,
"termlenght": termlenght,
"termbasis": termbasis,
"termvaluedeposited": termvaluedeposited,
"interestfrequency": interestfrequency,
"termdays": termdays,
"termmonths": termmonths,
"termyears": termyears,
"nominationRequired": nominationRequired,
"printNomineename": printNomineename,
"rdinstallmentvalue": rdinstallmentvalue,
"monthlyRDInstallmentdueday": monthlyRDInstallmentdueday,
"rdInstlFreq": rdInstlFreq,
'tpin': tpin,
},
);
return response.data;
}
}

View File

@@ -0,0 +1,102 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:send_message/send_message.dart' show sendSMS;
import 'package:simcards/sim_card.dart';
import 'package:simcards/simcards.dart';
// This enum provides detailed status back to the UI layer.
enum PermissionStatusResult { granted, denied, permanentlyDenied, restricted }
class SmsService {
final Simcards _simcards = Simcards();
/// Handles the requesting of SMS and Phone permissions.
/// Returns a detailed status: granted, denied, or permanentlyDenied.
Future<PermissionStatusResult> handleSmsPermission() async {
var smsStatus = await Permission.sms.status;
var phoneStatus = await Permission.phone.status;
// Check initial status
if (smsStatus.isGranted && phoneStatus.isGranted) {
return PermissionStatusResult.granted;
}
if (smsStatus.isPermanentlyDenied || phoneStatus.isPermanentlyDenied) {
return PermissionStatusResult.permanentlyDenied;
}
if (smsStatus.isRestricted || phoneStatus.isRestricted) {
return PermissionStatusResult.restricted;
}
// Request permissions if not granted
print("Requesting SMS and Phone permissions...");
await [Permission.phone, Permission.sms].request();
// Re-check status after request
smsStatus = await Permission.sms.status;
phoneStatus = await Permission.phone.status;
if (smsStatus.isGranted && phoneStatus.isGranted) {
return PermissionStatusResult.granted;
}
if (smsStatus.isPermanentlyDenied || phoneStatus.isPermanentlyDenied) {
return PermissionStatusResult.permanentlyDenied;
}
if (smsStatus.isRestricted || phoneStatus.isRestricted) {
return PermissionStatusResult.restricted;
}
// If none of the above, it's denied
return PermissionStatusResult.denied;
}
/// Tries to send a single verification SMS.
/// This should only be called AFTER permissions have been granted.
Future<bool> sendVerificationSms({
required BuildContext context,
required String destinationNumber,
required String message,
}) async {
try {
List<SimCard> simCardList = await _simcards.getSimCards();
if (simCardList.isEmpty) {
print("No SIM card detected.");
return false;
}
return await _sendSms(destinationNumber, message, simCardList.first);
} catch (e) {
print("An error occurred in the SMS process: $e");
return false;
}
}
/// Private function to perform the SMS sending action.
Future<bool> _sendSms(
String destinationNumber, String message, SimCard selectedSim) async {
if (Platform.isAndroid) {
try {
String smsMessage = message;
String result = await sendSMS(
message: smsMessage,
recipients: [destinationNumber],
sendDirect: true,
);
print("Background SMS send attempt result: $result");
if (result.toLowerCase().contains('sent')) {
print("Success: SMS appears to have been sent.");
return true;
} else {
print("Failure: SMS was not sent. Result: $result");
return false;
}
} catch (e) {
print("Error attempting to send SMS directly: $e");
return false;
}
} else {
print("SMS sending is only supported on Android.");
return false;
}
}
}

View File

@@ -1,334 +0,0 @@
import 'dart:developer';
import 'package:dio/dio.dart';
class YojnaService {
final Dio _dio;
YojnaService(this._dio);
Future<dynamic> fetchpmydetails({
required String scheme,
required String action,
required String accountno,
}) async {
try {
final response = await _dio.post(
"/api/gov-scheme/req/PMJBY",
data: {
'scheme': scheme,
'action': action,
'accountNo': accountno,
},
options: Options(
headers: {
"Content-Type": "application/json",
},
),
);
log("PMY Details Response: ${response.data}");
if (response.statusCode == 200) {
return response.data;
} else {
throw Exception("INTERNAL SERVER ERROR");
}
} catch (e) {
log("Error fetching PMY details: $e");
return null;
}
}
Future secondvalidationPMJJBY({
String? aadharno,
String? accountno,
String? availablebalance,
String? country,
String? customerdob,
String? customername,
String? customerno,
String? dateofacctopening,
String? emailid,
String? financialyear,
String? gender,
String? ifsccode,
String? married,
String? mobileno,
String? pan,
String? pincode,
String? policynumber,
String? premiumamount,
String? state,
String? healthstatus,
String? collectionchannel,
String? nomineename,
String? nomineeaddress,
String? nomineerelationship,
String? nomineeminor,
String? ruralcategory,
}) async {
final response = await _dio.post(
'/api/gov-scheme/create/PMJBY',
options: Options(
validateStatus: (int? status) => true,
receiveDataWhenStatusError: true,
),
data: {
'aadharno': aadharno ,
'accountno': accountno,
'availablebalance': availablebalance,
'country': country,
'customerdob': customerdob,
'customername': customername,
'customerno': customerno,
'dateofacctopening': dateofacctopening,
'emailid': emailid,
'financialyear': financialyear,
'gender': gender,
'ifsccode': ifsccode,
'married': married,
'mobileno': mobileno,
'pan': pan,
'pincode': pincode,
'policynumber': policynumber,
'premiumamount': premiumamount,
'state': state,
'healthstatus': healthstatus,
'collectionchannel': collectionchannel,
'nomineename': nomineename,
'nomineeaddress': nomineeaddress,
'nomineerelationship': nomineerelationship,
'nomineeminor': nomineeminor,
'ruralcategory': ruralcategory,
},
);
return response.toString();
}
Future secondvalidationPMSBY({
String? aadharno,
String? accountno,
String? address1,
String? address2,
String? availablebalance,
String? city,
String? country,
String? customerdob,
String? customername,
String? customerno,
String? emailid,
String? financialyear,
String? gender,
String? married,
String? mobileno,
String? pan,
String? pincode,
String? policyno,
String? premiumamount,
String? state,
String? healthstatus,
String? nomineename,
String? nomineeadress,
String? relationwithnominee,
String? nomineeminor,
String? collectionchannel,
String? ruralcategory,
String? policystatus,
}) async {
final response = await _dio.post(
'/api/gov-scheme/create/PMSBY',
options: Options(
validateStatus: (int? status) => true,
receiveDataWhenStatusError: true,
),
data: {
"aadharno": aadharno,
"accountno": accountno,
"address1": address1,
"address2": address2,
"availablebalance": availablebalance,
"city": city,
"country": country,
"customerdob": customerdob,
"customername": customername,
"customerno": customerno,
"emailid": emailid,
"financialyear": financialyear,
"gender": gender,
"married": married,
"mobileno": mobileno,
"pan": pan,
"pincode": pincode,
"policyno": policyno,
"premiumamount": premiumamount,
"state": state,
"healthstatus": healthstatus,
"nomineename": nomineename,
"nomineeadress": nomineeadress,
"relationwithnominee": relationwithnominee,
"nomineeminor": nomineeminor,
"collectionchannel": collectionchannel,
"ruralcategory": ruralcategory,
"policystatus": policystatus,
},
);
return response.toString();
}
Future<dynamic> enquiry({
required String scheme,
required String action,
required String financialyear,
String? customerno,
}) async {
try {
final response = await _dio.get(
"/api/gov-scheme/enquiry/PMJBY",
queryParameters: {
'scheme': scheme,
'action': action,
'financialyear': financialyear,
'customerno': customerno,
},
options: Options(
headers: {
"Content-Type": "application/json",
},
),
);
log("PMY Details Response: ${response.data}");
if (response.statusCode == 200) {
return response.data;
} else {
throw Exception("INTERNAL SERVER ERROR");
}
} catch (e) {
log("Error fetching PMY details: $e");
return null;
}
}
Future<dynamic> fetchpapydetails({
required String accountno,
}) async {
try {
final response = await _dio.post(
"/api/apy/req/APY",
data: {
'accountNo': accountno,
},
options: Options(
headers: {
"Content-Type": "application/json",
},
),
);
log("PMY Details Response: ${response.data}");
if (response.statusCode == 200) {
return response.data;
} else {
throw Exception("INTERNAL SERVER ERROR");
}
} catch (e) {
log("Error fetching APY details: $e");
return null;
}
}
Future registerAPY({
String? accountno,
String? customerfirstname,
String? customermiddlename,
String? customerlastname,
String? availablebalance,
String? customerdob,
String? emailid,
String? gender,
String? married,
String? nomineename,
String? relationwithsubscriber,
String? mobilenumber,
String? nomineeminor,
String? customerno,
String? beneficaryofothersociatysecurityschemes,
String? whetherincometaxpayer,
String? customertitle,
String? aadharno,
String? nameofspouse,
String? ageofjoining,
String? pensionamtoptedfor,
String? montlycontributioncalculate,
String? collectionchannel,
String? subsequentContributionDebitDate,
String? secondnomineeminor,
String? secondnomineename,
String? secondrelationshipwithsubscriber,
String? pincode,
String? fatcacrsapplicable,
String? countryofbirth,
String? countryofcitizenship,
String? countryofresidencefortaxpurpose,
String? uspersonflag,
String? fatcadeclarationcount,
String? documentevidencingcitizenshipflag,
String? reasonfornoevidence,
String? nameofdocumentforcitizenshipevidence,
String? modeofcollection,
String? contributionType,
}) async {
final response = await _dio.post(
'/api/apy/create/APY',
options: Options(
validateStatus: (int? status) => true,
receiveDataWhenStatusError: true,
),
data: {
'accountno':accountno,
'customerfirstname':customerfirstname,
'customermiddlename':customermiddlename,
'customerlastname':customerlastname,
'availablebalance':availablebalance,
'customerdob':customerdob,
'emailid':emailid,
'gender':gender,
'married':married,
'nomineename':nomineename,
'relationwithsubscriber':relationwithsubscriber,
'mobilenumber':mobilenumber,
'nomineeminor':nomineeminor,
'customerno':customerno,
'beneficaryofothersociatysecurityschemes':beneficaryofothersociatysecurityschemes,
'whetherincometaxpayer':whetherincometaxpayer,
'customertitle':customertitle,
'aadharno':aadharno,
'nameofspouse':nameofspouse,
'ageofjoining':ageofjoining,
'pensionamtoptedfor':pensionamtoptedfor,
'montlycontributioncalculate':montlycontributioncalculate,
'collectionchannel':collectionchannel,
'subsequentContributionDebitDate':subsequentContributionDebitDate,
'secondnomineeminor':secondnomineeminor,
'secondnomineename':secondnomineename,
'secondrelationshipwithsubscriber':secondrelationshipwithsubscriber,
'pincode':pincode,
'fatcacrsapplicable':fatcacrsapplicable,
'countryofbirth':countryofbirth,
'countryofcitizenship':countryofcitizenship,
'countryofresidencefortaxpurpose':countryofresidencefortaxpurpose,
'uspersonflag':uspersonflag,
'fatcadeclarationcount':fatcadeclarationcount,
'documentevidencingcitizenshipflag':documentevidencingcitizenshipflag,
'reasonfornoevidence':reasonfornoevidence,
'nameofdocumentforcitizenshipevidence':nameofdocumentforcitizenshipevidence,
'modeofcollection':modeofcollection,
'contributionType':contributionType,
},
);
return response.toString();
}
}

View File

@@ -6,7 +6,6 @@ class Beneficiary {
final String ifscCode;
final String? bankName;
final String? branchName;
final String? transactionLimit;
final String? tpin;
Beneficiary({
@@ -17,7 +16,6 @@ class Beneficiary {
required this.ifscCode,
this.bankName,
this.branchName,
this.transactionLimit,
this.tpin,
});
@@ -32,7 +30,6 @@ class Beneficiary {
ifscCode: json['ifsc_code'] ?? json['ifscCode'] ?? '',
bankName: json['bank_name'] ?? json['bankName'] ?? '',
branchName: json['branch_name'] ?? json['branchName'] ?? '',
transactionLimit: json['transactionLimit'] ?? '',
);
}

View File

@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/branch_service.dart';
import 'package:kmobile/api/services/cheque_service.dart';
import 'package:kmobile/api/services/deposit_service.dart';
import 'package:kmobile/api/services/limit_service.dart';
import 'package:kmobile/api/services/rtgs_service.dart';
import 'package:kmobile/api/services/neft_service.dart';
@@ -11,7 +10,6 @@ import 'package:dio/dio.dart';
import 'package:kmobile/api/services/beneficiary_service.dart';
import 'package:kmobile/api/services/payment_service.dart';
import 'package:kmobile/api/services/user_service.dart';
import 'package:kmobile/api/services/yojna_service.dart';
import 'package:kmobile/data/repositories/transaction_repository.dart';
import 'package:kmobile/features/auth/controllers/theme_cubit.dart';
import 'package:kmobile/features/auth/controllers/theme_mode_cubit.dart';
@@ -60,8 +58,6 @@ Future<void> setupDependencies() async {
getIt.registerSingleton<ImpsService>(ImpsService(getIt<Dio>()));
getIt.registerSingleton<BranchService>(BranchService(getIt<Dio>()));
getIt.registerSingleton<ChequeService>(ChequeService(getIt<Dio>()));
getIt.registerSingleton<YojnaService>(YojnaService(getIt<Dio>()));
getIt.registerSingleton<DepositService>(DepositService(getIt<Dio>()));
getIt.registerLazySingleton<ChangePasswordService>(
() => ChangePasswordService(getIt<Dio>()),
);
@@ -80,7 +76,7 @@ Dio _createDioClient() {
final dio = Dio(
BaseOptions(
baseUrl:
'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com', //test
'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com', //test
//'http://lb-kccb-mobile-banking-app-848675342.ap-south-1.elb.amazonaws.com', //prod
//'https://kccbmbnk.net', //prod small
connectTimeout: const Duration(seconds: 60),

View File

@@ -1,199 +0,0 @@
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/deposit_service.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/l10n/app_localizations.dart';
import 'package:kmobile/features/account_opening/screens/create_deposit_screen.dart';
class AccountOpeningScreen extends StatefulWidget {
final List<User> users;
final int selectedIndex;
const AccountOpeningScreen({
super.key,
required this.users,
required this.selectedIndex,
});
@override
State<AccountOpeningScreen> createState() => _AccountOpeningScreenState();
}
class _AccountOpeningScreenState extends State<AccountOpeningScreen> {
User? _selectedAccount;
List<User> _filteredUsers = [];
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
@override
void initState() {
super.initState();
_filteredUsers = widget.users
.where((user) => ['SA', 'SB', 'CA', 'CC'].contains(user.accountType))
.toList();
// Pre-fill the account number if possible
if (widget.users.isNotEmpty && widget.selectedIndex < widget.users.length) {
if (_filteredUsers.isNotEmpty) {
if (_filteredUsers.contains(widget.users[widget.selectedIndex])) {
_selectedAccount = widget.users[widget.selectedIndex];
} else {
_selectedAccount = _filteredUsers.first;
}
} else {
_selectedAccount = widget.users[widget.selectedIndex];
}
} else {
if (_filteredUsers.isNotEmpty) {
_selectedAccount = _filteredUsers.first;
}
}
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(l10n.accountOpeningDeposit),
centerTitle: false,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
l10n.accountOpeningDescription,
style: Theme.of(context).textTheme.titleMedium,
),
),
),
const SizedBox(height: 16),
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DropdownButtonFormField<User>(
value: _selectedAccount,
decoration: InputDecoration(
labelText: l10n.accountNumber,
border: const OutlineInputBorder(),
contentPadding: const EdgeInsets.symmetric(
vertical: 20, horizontal: 12),
),
items: _filteredUsers.map((user) {
return DropdownMenuItem<User>(
value: user,
child: Text(user.accountNo.toString()),
);
}).toList(),
onChanged: (User? newUser) {
setState(() {
_selectedAccount = newUser;
});
},
validator: (value) {
if (value == null) {
return l10n.accountNumberRequired;
}
return null;
},
),
],
),
),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _isLoading
? null
: () async {
if (_formKey.currentState!.validate() &&
_selectedAccount != null) {
setState(() {
_isLoading = true;
});
try {
final response = await getIt<DepositService>()
.fetchaccountdetails(
accountno:
_selectedAccount!.accountNo.toString());
if (response != null) {
if (mounted) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CreateDepositScreen(
selectedAccount: _selectedAccount!,
initialData: response,
),
),
);
}
} else {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
l10n.failedToFetchAccountDetails)),
);
}
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Error: $e")),
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
},
style: ElevatedButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.primaryContainer,
foregroundColor:
Theme.of(context).colorScheme.onPrimaryContainer,
minimumSize: const Size(double.infinity, 50),
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: _isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: Text(
l10n.proceedButton,
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.bold),
),
),
],
),
),
),
);
}
}

View File

@@ -1,714 +0,0 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/deposit_service.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/features/fund_transfer/screens/transaction_pin_screen.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class CreateDepositScreen extends StatefulWidget {
final User selectedAccount;
final Map<String, dynamic>? initialData;
const CreateDepositScreen({
super.key,
required this.selectedAccount,
this.initialData,
});
@override
State<CreateDepositScreen> createState() => _CreateDepositScreenState();
}
class _CreateDepositScreenState extends State<CreateDepositScreen> {
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
// Controllers
late final _fromAcctNoController =
TextEditingController(text: widget.selectedAccount.accountNo.toString());
late final _cifNoController = TextEditingController(
text: widget.initialData?['cifNo']?.toString());
late final _idNoController = TextEditingController(
text: widget.initialData?['idno']?.toString());
late final _customerNameController = TextEditingController(
text: widget.initialData?['customername']?.toString());
late final _nationalityController = TextEditingController(
text: widget.initialData?['nationality']?.toString());
late final _addressLine1Controller = TextEditingController(
text: widget.initialData?['addressline1']?.toString());
late final _addressLine2Controller = TextEditingController(
text: widget.initialData?['addressline2']?.toString());
late final _pincodeController = TextEditingController(
text: widget.initialData?['pincode']?.toString());
late final _productController = TextEditingController();
late final _typeController = TextEditingController();
late final _customerCategoryController = TextEditingController();
late final _termLocationController = TextEditingController();
late final _currencyController = TextEditingController();
late final _acctSgmtCodeController = TextEditingController();
late final _interestPaymentMethodController = TextEditingController();
late final _taxFileNumberIndicatorController = TextEditingController();
late final _termLengthController = TextEditingController();
late final _termBasisController = TextEditingController();
late final _termValueDepositedController = TextEditingController();
late final _interestFrequencyController = TextEditingController();
late final _termDaysController = TextEditingController();
late final _termMonthsController = TextEditingController();
late final _termYearsController = TextEditingController();
late final _nominationRequiredController = TextEditingController();
late final _printNomineeNameController = TextEditingController();
// RD specific controllers
late final _rdInstallmentValueController = TextEditingController();
late final _monthlyRDInstallmentDueDayController = TextEditingController();
late final _rdInstlFreqController = TextEditingController();
Map<String, String> get _productOptions {
final l10n = AppLocalizations.of(context);
return {
'20': l10n.termDepositOption,
'25': l10n.fixedDepositOption,
'28': l10n.recurringDepositOption,
};
}
Map<String, String> get _typeOptions {
final l10n = AppLocalizations.of(context);
return {
'01': l10n.memberOption,
'02': l10n.nonMemberOption,
'03': l10n.staffOption,
'04': l10n.govtOption,
'05': l10n.schoolOption,
'06': l10n.panchayatOption,
'07': l10n.trustsOption,
'08': l10n.municipalCouncilOption,
'09': l10n.nroOption,
'10': l10n.bankOption,
'11': l10n.noFrillOption,
'12': l10n.specialSchemesOption,
'13': l10n.minorOption,
};
}
Map<String, String> get _customerCategoryOptions {
final l10n = AppLocalizations.of(context);
return {
'1': l10n.publicIndividualOption,
'2': l10n.societiesOption,
'3': l10n.seniorCitizenOption,
'5': l10n.governmentOption,
'6': l10n.localBodiesOption,
'7': l10n.otherOption,
};
}
Map<String, String> get _termLocationOptions {
final l10n = AppLocalizations.of(context);
return {
'10': l10n.sbCaOption,
'13': l10n.days46_90Option,
'14': l10n.days91_180Option,
'15': l10n.days180_1YearOption,
'16': l10n.year1_18MonthsOption,
'20': l10n.months18_2YearsOption,
'17': l10n.years2_3Option,
'18': l10n.years3_10Option,
'22': l10n.above10YearsOption,
'24': l10n.devKanyaYojnaOption,
'25': l10n.hazarDainLakhOption,
};
}
Map<String, String> get _currencyOptions {
final l10n = AppLocalizations.of(context);
return {
'1': l10n.currencyINR,
};
}
Map<String, String> get _acctSgmtCodeOptions {
final l10n = AppLocalizations.of(context);
return {
'0706': l10n.publicIndividualOption,
'5002': l10n.societiesOption,
'5005': l10n.governmentOption,
'5050': l10n.glOthersOption,
};
}
Map<String, String> get _interestPaymentMethodOptions {
final l10n = AppLocalizations.of(context);
return {
'R': l10n.reInvestOption,
};
}
Map<String, String> get _termBasisOptions {
final l10n = AppLocalizations.of(context);
return {
'D': l10n.daysOption,
'M': l10n.monthsOption,
};
}
Map<String, String> get _interestFrequencyOptions {
final l10n = AppLocalizations.of(context);
return {
'M': l10n.onMaturityOption,
'1M': l10n.monthlyOption,
'3M': l10n.quarterlyOption,
'1A': l10n.anniversaryMonthlyOption,
'3A': l10n.anniversaryQuarterlyOption,
};
}
Map<String, String> get _rdInstlFreqOptions {
final l10n = AppLocalizations.of(context);
return {
'M': l10n.monthlyOption,
};
}
@override
void dispose() {
_fromAcctNoController.dispose();
_cifNoController.dispose();
_idNoController.dispose();
_customerNameController.dispose();
_nationalityController.dispose();
_addressLine1Controller.dispose();
_addressLine2Controller.dispose();
_pincodeController.dispose();
_productController.dispose();
_typeController.dispose();
_customerCategoryController.dispose();
_termLocationController.dispose();
_currencyController.dispose();
_acctSgmtCodeController.dispose();
_interestPaymentMethodController.dispose();
_taxFileNumberIndicatorController.dispose();
_termLengthController.dispose();
_termBasisController.dispose();
_termValueDepositedController.dispose();
_interestFrequencyController.dispose();
_termDaysController.dispose();
_termMonthsController.dispose();
_termYearsController.dispose();
_nominationRequiredController.dispose();
_printNomineeNameController.dispose();
_rdInstallmentValueController.dispose();
_monthlyRDInstallmentDueDayController.dispose();
_rdInstlFreqController.dispose();
super.dispose();
}
void _handleCreate() {
if (_formKey.currentState!.validate()) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TransactionPinScreen(
onPinCompleted: (ctx, pin) async {
Navigator.pop(context);
_executeCreation(pin);
},
),
),
);
}
}
void _executeCreation(String pin) async {
setState(() {
_isLoading = true;
});
try {
final product = _productController.text;
dynamic response;
if (product == '20' || product == '25') {
response = await getIt<DepositService>().createFdTd(
fromacctno: _fromAcctNoController.text,
cifNo: _cifNoController.text,
idno: _idNoController.text,
customername: _customerNameController.text,
nationality: _nationalityController.text,
addressline1: _addressLine1Controller.text,
addressline2: _addressLine2Controller.text,
pincode: _pincodeController.text,
product: _productController.text,
type: _typeController.text,
customercategory: _customerCategoryController.text,
termlocation: _termLocationController.text,
currency: _currencyController.text,
acctsgmtcode: _acctSgmtCodeController.text,
interestpaymentmethod: _interestPaymentMethodController.text,
taxfilenumberindicator: _taxFileNumberIndicatorController.text,
termlenght: _termLengthController.text,
termbasis: _termBasisController.text,
termvaluedeposited: _termValueDepositedController.text,
interestfrequency: _interestFrequencyController.text,
termdays: _termDaysController.text,
termmonths: _termMonthsController.text,
termyears: _termYearsController.text,
nominationRequired: _nominationRequiredController.text,
printNomineename: _printNomineeNameController.text,
tpin: pin,
);
} else if (product == '28') {
response = await getIt<DepositService>().createRd(
fromacctno: _fromAcctNoController.text,
cifNo: _cifNoController.text,
idno: _idNoController.text,
customername: _customerNameController.text,
nationality: _nationalityController.text,
addressline1: _addressLine1Controller.text,
addressline2: _addressLine2Controller.text,
pincode: _pincodeController.text,
product: _productController.text,
type: _typeController.text,
customercategory: _customerCategoryController.text,
termlocation: _termLocationController.text,
currency: _currencyController.text,
acctsgmtcode: _acctSgmtCodeController.text,
interestpaymentmethod: _interestPaymentMethodController.text,
taxfilenumberindicator: _taxFileNumberIndicatorController.text,
termlenght: _termLengthController.text,
termbasis: _termBasisController.text,
termvaluedeposited: _termValueDepositedController.text,
interestfrequency: _interestFrequencyController.text,
termdays: _termDaysController.text,
termmonths: _termMonthsController.text,
termyears: _termYearsController.text,
nominationRequired: _nominationRequiredController.text,
printNomineename: _printNomineeNameController.text,
rdinstallmentvalue: _rdInstallmentValueController.text,
monthlyRDInstallmentdueday: _monthlyRDInstallmentDueDayController.text,
rdInstlFreq: _rdInstlFreqController.text,
tpin: pin,
);
}
if (response != null && response is Map<String, dynamic>) {
if (mounted) {
final l10n = AppLocalizations.of(context);
if (response['error'] == 'INCORRECT_TPIN' || response['message'] == 'INCORRECT_TPIN') {
_showSimpleDialog(l10n.invalidTpin, l10n.incorrectTpinMessage);
} else if (response['status'] == 'FAILED') {
_showFailureDialog(response['message']?.toString());
} else if (response.containsKey('accountno')) {
_showResponseDialog(response);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.unexpectedResponse)),
);
}
}
} else {
if (mounted) {
final l10n = AppLocalizations.of(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.failedToCreateDeposit)),
);
}
}
} on DioException catch (e) {
if (mounted) {
final l10n = AppLocalizations.of(context);
try {
final errorBody = e.response?.data;
if (errorBody is Map && (errorBody['error'] == 'INCORRECT_TPIN' || errorBody['message'] == 'INCORRECT_TPIN')) {
_showSimpleDialog(l10n.invalidTpin, l10n.incorrectTpinMessage);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("${l10n.error}: ${e.message}")),
);
}
} catch (_) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.internalServerError)),
);
}
}
} catch (e) {
if (mounted) {
final l10n = AppLocalizations.of(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("${l10n.error}: $e")),
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
Future<void> _showSimpleDialog(String title, String message) async {
final l10n = AppLocalizations.of(context);
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text(title),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text(message),
],
),
),
actions: <Widget>[
TextButton(
child: Text(l10n.ok),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
void _showFailureDialog(String? apiMessage) {
final l10n = AppLocalizations.of(context);
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Row(
children: [
Icon(Icons.error_outline, color: Theme.of(context).colorScheme.error),
const SizedBox(width: 8),
Text(l10n.creationFailed),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.accountCreationFailed,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
if (apiMessage != null) ...[
Text(l10n.reason(apiMessage)),
const SizedBox(height: 12),
],
Text(
l10n.creationFailedDescription,
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(l10n.ok),
),
],
),
);
}
void _showResponseDialog(Map<String, dynamic> response) {
final l10n = AppLocalizations.of(context);
final accountNo = response['accountno']?.toString() ?? 'N/A';
final accountTypeRaw = response['accounttype']?.toString() ?? '';
final accountTypeText = _productOptions[accountTypeRaw] ?? accountTypeRaw;
final amount = response['amount']?.toString() ?? '0.0';
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: Text(l10n.depositCreatedSuccessfully),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("${l10n.accountNumberLabel}: $accountNo"),
const SizedBox(height: 8),
Text("${l10n.accountTypeLabel}: $accountTypeText"),
const SizedBox(height: 8),
Text(l10n.amountValue(amount)),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).popUntil((route) => route.isFirst);
},
child: Text(l10n.ok),
),
],
),
);
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(l10n.createDeposit),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Tile 1: Customer Info
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.customerInformation,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildTextField(_fromAcctNoController, l10n.fromAccountNo,
mandatory: true, readOnly: true),
_buildTextField(_cifNoController, l10n.cifNo),
_buildTextField(_idNoController, l10n.idNo),
_buildTextField(_customerNameController, l10n.customerName),
_buildTextField(_nationalityController, l10n.nationality),
_buildTextField(_addressLine1Controller, l10n.addressLine1),
_buildTextField(_addressLine2Controller, l10n.addressLine2),
_buildTextField(_pincodeController, l10n.pincodeLabel,
keyboardType: TextInputType.number),
],
),
),
),
const SizedBox(height: 16),
// Tile 2: Deposit Details
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.depositDetails,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildDropdownField(
_productController, l10n.product, _productOptions,
mandatory: true, onChanged: (val) {
setState(() {
_productController.text = val ?? '';
});
}),
_buildDropdownField(_typeController, l10n.type, _typeOptions,
mandatory: true),
_buildDropdownField(_customerCategoryController,
l10n.customerCategory, _customerCategoryOptions,
mandatory: true),
_buildDropdownField(_termLocationController,
l10n.termLocation, _termLocationOptions,
mandatory: true),
_buildDropdownField(
_currencyController, l10n.currency, _currencyOptions,
mandatory: true),
_buildDropdownField(_acctSgmtCodeController,
l10n.accountSegmentCode, _acctSgmtCodeOptions,
mandatory: true),
],
),
),
),
const SizedBox(height: 16),
// Tile 3: Interest & Term Settings
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.interestTermSettings,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildDropdownField(_interestPaymentMethodController,
l10n.interestPaymentMethod, _interestPaymentMethodOptions,
mandatory: true),
_buildTextField(_taxFileNumberIndicatorController,
l10n.taxFileNumberIndicator),
_buildTextField(_termLengthController, l10n.termLength),
_buildDropdownField(
_termBasisController, l10n.termBasis, _termBasisOptions,
mandatory: true),
_buildTextField(_termValueDepositedController,
l10n.termValueDeposited,
keyboardType: TextInputType.number,
mandatory: _productController.text != '28'),
_buildDropdownField(_interestFrequencyController,
l10n.interestFrequency, _interestFrequencyOptions),
_buildTextField(_termDaysController, l10n.termDays,
keyboardType: TextInputType.number),
_buildTextField(_termMonthsController, l10n.termMonths,
keyboardType: TextInputType.number),
_buildTextField(_termYearsController, l10n.termYears,
keyboardType: TextInputType.number, mandatory: true),
_buildTextField(
_nominationRequiredController, l10n.nominationRequired),
_buildTextField(
_printNomineeNameController, l10n.printNomineeName),
],
),
),
),
const SizedBox(height: 16),
// Tile 4: RD Specific Fields (Conditional)
if (_productController.text == '28')
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.recurringDepositSettings,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildTextField(_rdInstallmentValueController,
l10n.rdInstallmentValue,
keyboardType: TextInputType.number),
_buildTextField(_monthlyRDInstallmentDueDayController,
l10n.monthlyRDInstallmentDueDay,
keyboardType: TextInputType.number),
_buildDropdownField(_rdInstlFreqController,
l10n.rdInstallmentFrequency, _rdInstlFreqOptions),
],
),
),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _isLoading ? null : _handleCreate,
style: ElevatedButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.primaryContainer,
foregroundColor:
Theme.of(context).colorScheme.onPrimaryContainer,
padding: const EdgeInsets.symmetric(vertical: 16),
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: _isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: Text(
l10n.proceedButton,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 32),
],
),
),
),
);
}
Widget _buildDropdownField(TextEditingController controller, String label,
Map<String, String> options,
{bool readOnly = false,
bool mandatory = false,
void Function(String?)? onChanged}) {
final l10n = AppLocalizations.of(context);
String? currentValue =
options.containsKey(controller.text) ? controller.text : null;
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: DropdownButtonFormField<String>(
value: currentValue,
onChanged: readOnly || _isLoading
? null
: (newValue) {
if (newValue != null) {
setState(() {
controller.text = newValue;
});
}
if (onChanged != null) {
onChanged(newValue);
}
},
decoration: InputDecoration(
labelText: mandatory ? '$label *' : label,
border: const OutlineInputBorder(),
contentPadding:
const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
),
validator: (value) {
if (mandatory && (value == null || value.isEmpty)) {
return l10n.fieldRequired;
}
return null;
},
items: options.entries.map((entry) {
return DropdownMenuItem<String>(
value: entry.key,
child: Text(entry.value),
);
}).toList(),
),
);
}
Widget _buildTextField(TextEditingController controller, String label,
{TextInputType keyboardType = TextInputType.text,
bool readOnly = false,
bool mandatory = false,
VoidCallback? onTap}) {
final l10n = AppLocalizations.of(context);
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: TextFormField(
controller: controller,
readOnly: readOnly || onTap != null || _isLoading,
onTap: _isLoading ? null : onTap,
decoration: InputDecoration(
labelText: mandatory ? '$label *' : label,
border: const OutlineInputBorder(),
suffixIcon: onTap != null ? const Icon(Icons.calendar_today) : null,
contentPadding:
const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
),
keyboardType: keyboardType,
validator: (value) {
if (mandatory && (value == null || value.isEmpty)) {
return l10n.fieldRequired;
}
return null;
},
),
);
}
}

View File

@@ -3,6 +3,7 @@ import 'package:kmobile/app.dart';
import 'package:kmobile/features/auth/screens/mpin_screen.dart';
import 'package:kmobile/features/auth/screens/set_password_screen.dart';
import 'package:kmobile/features/auth/screens/tnc_required_screen.dart';
import 'package:kmobile/features/auth/screens/verification_screen.dart';
import 'package:kmobile/widgets/tnc_dialog.dart';
import '../../../l10n/app_localizations.dart';
import 'package:flutter/material.dart';
@@ -30,12 +31,23 @@ class LoginScreenState extends State<LoginScreen>
super.dispose();
}
void _submitForm() {
void _submitForm() async {
if (_formKey.currentState!.validate()) {
context.read<AuthCubit>().login(
_customerNumberController.text.trim(),
_passwordController.text,
);
final bool? verificationSuccess = await Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => VerificationScreen(
customerNo: _customerNumberController.text.trim(),
password: _passwordController.text,
),
),
);
if (verificationSuccess == true && mounted) {
context.read<AuthCubit>().login(
_customerNumberController.text.trim(),
_passwordController.text,
);
}
}
}

View File

@@ -0,0 +1,177 @@
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/send_sms_service.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:uuid/uuid.dart';
class SmsVerificationHelper {
final SmsService _smsService = SmsService();
Future<void> _showPermanentlyDeniedDialog(BuildContext context) async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("Permission Required"),
content: const Text(
"SMS and Phone permissions are required for device verification. Please enable them in your app settings to continue."),
actions: [
TextButton(
child: const Text("Cancel"),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: const Text("Open Settings"),
onPressed: () {
openAppSettings(); // Opens the phone's settings screen for this app
Navigator.of(context).pop();
},
),
],
),
);
}
Future<void> _showRestrictedSmsDialog(BuildContext context) async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("SMS Permission Restricted"),
content: const SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"It seems your device is restricting this app from sending SMS messages, which is required for verification. Please follow these steps to enable it:\n"),
Text("1. Open your device Settings.",
style: TextStyle(fontWeight: FontWeight.bold)),
Text("2. Go to 'Apps' or 'Apps & notifications'."),
Text("3. Find and tap on this app ('KMobile')."),
Text("4. Tap on the three dots (⋮) in the top right corner."),
Text(
"5. Select 'Allow restricted settings' and confirm. This is crucial to allow SMS permission."),
Text("6. Now you have two options to allow SMS permission:"),
Text(
" a. Tap on 'Permissions', then find 'SMS' is set to 'Allow'."),
Text(
" b. Alternatively, you can return to the KMobile app, and the SMS permission pop-up should appear again, allowing you to grant it directly."),
Text(
"\nSome devices have an additional setting for 'Premium SMS'. If the above doesn't work, look for a 'Premium SMS access' setting (you can search for it in your Settings app) and set it to 'Always Allow' for this app.\n"),
Text(
"After you've enabled the permission, please come back to the app."),
],
),
),
actions: [
TextButton(
child: const Text("I've Enabled It"),
onPressed: () => Navigator.of(context).pop(),
),
],
),
);
}
Future<String?> initiateSmsSequence({
required BuildContext context,
}) async {
bool hasPermission = false;
// --- PERMISSION LOOP ---
while (!hasPermission) {
// handleSmsPermission will check the status and request if not granted.
final status = await _smsService.handleSmsPermission();
switch (status) {
case PermissionStatusResult.granted:
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Permissions Granted! Proceeding..."),
duration: Duration(seconds: 2)),
);
hasPermission = true; // This will break the loop
break;
case PermissionStatusResult.denied:
// The user denied the permission. We show a dialog to explain why we need it
// and give them a chance to cancel or let the loop try again.
final tryAgain = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text("Permission Required"),
content: const Text(
"This app requires SMS and Phone permissions to verify your device. Please grant the permissions to continue."),
actions: [
TextButton(
child: const Text("Cancel"),
onPressed: () => Navigator.of(context).pop(false),
),
TextButton(
child: const Text("Try Again"),
onPressed: () => Navigator.of(context).pop(true),
),
],
),
);
if (tryAgain != true) {
return null; // User chose to cancel.
}
// If they chose "Try Again", the loop will repeat.
break;
case PermissionStatusResult.permanentlyDenied:
await _showPermanentlyDeniedDialog(context);
// Give user time to come back from settings
await Future.delayed(const Duration(seconds: 5));
// The loop will repeat and re-check the status.
break;
case PermissionStatusResult.restricted:
await _showRestrictedSmsDialog(context);
// Give user time to come back from settings
await Future.delayed(const Duration(seconds: 5));
// The loop will repeat and re-check the status.
break;
}
}
// --- SMS SENDING LOOP ---
// This part will only be reached if hasPermission is true.
int retries = 3;
while (retries > 0) {
var uuid = const Uuid();
String uniqueId = uuid.v4();
String smsMessage = uniqueId;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text("Attempting to send verification SMS... (${4 - retries})"),
duration: const Duration(seconds: 2)),
);
bool isSmsSent = await _smsService.sendVerificationSms(
context: context,
destinationNumber: '9580079717', // Replace with your number
message: smsMessage,
);
if (isSmsSent) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("SMS sent successfully!"),
duration: Duration(seconds: 2)),
);
return uniqueId;
} else {
retries--;
if (retries > 0) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("SMS failed to send. Retrying in 5 seconds..."),
duration: Duration(seconds: 4)),
);
await Future.delayed(const Duration(seconds: 5));
}
}
}
// If all retries fail
return null;
}
}

View File

@@ -0,0 +1,165 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/auth_service.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/features/auth/screens/sms_verification_helper.dart';
class VerificationScreen extends StatefulWidget {
final String customerNo;
final String password;
const VerificationScreen({
super.key,
required this.customerNo,
required this.password,
});
@override
State<VerificationScreen> createState() => _VerificationScreenState();
}
class _VerificationScreenState extends State<VerificationScreen> {
String _statusMessage = "Starting verification...";
Timer? _timer;
int _countdown = 120;
bool _isVerifying = false;
bool _verificationFailed = false;
@override
void initState() {
super.initState();
_startVerificationProcess();
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
void _startTimer() {
_timer?.cancel(); // Cancel any existing timer
_countdown = 120;
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (_countdown > 0) {
setState(() {
_countdown--;
});
} else {
_timer?.cancel();
if (mounted) {
setState(() {
_statusMessage = "Verification timed out. Please try again.";
_isVerifying = false;
_verificationFailed = true;
});
}
}
});
}
Future<void> _startVerificationProcess() async {
setState(() {
_isVerifying = true;
_verificationFailed = false;
_statusMessage = "Starting verification...";
});
_startTimer();
// 1. Send SMS
setState(() {
_statusMessage = "SMS sending...";
});
final smsHelper = SmsVerificationHelper();
final uuid = await smsHelper.initiateSmsSequence(context: context);
if (uuid != null && mounted) {
// SMS sending was successful, now wait before verifying.
setState(() {
_statusMessage = "SMS sent. Waiting for network delivery...";
});
// Adding a 10-second delay to account for SMS network latency.
await Future.delayed(const Duration(seconds: 10));
if (!mounted) return;
// 2. Verify SIM
setState(() {
_statusMessage = "Verifying with server...";
});
final authService = getIt<AuthService>();
try {
await authService.simVerify(uuid, widget.customerNo);
setState(() {
_statusMessage = "Verification successful!";
_isVerifying = false;
});
_timer?.cancel();
// Pop with success result
Navigator.of(context).pop(true);
} catch (e) {
setState(() {
_statusMessage = e.toString();
_isVerifying = false;
_verificationFailed = true;
});
}
} else if (mounted) {
setState(() {
_statusMessage =
"SMS sending failed. Please check permissions and try again.";
_isVerifying = false;
_verificationFailed = true;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Device Verification"),
automaticallyImplyLeading: !_isVerifying,
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_isVerifying) const CircularProgressIndicator(),
if (!_isVerifying && _verificationFailed)
const Icon(Icons.error_outline, color: Colors.red, size: 50),
if (!_isVerifying && !_verificationFailed)
const Icon(Icons.check_circle_outline,
color: Colors.green, size: 50),
const SizedBox(height: 32),
Text(
_statusMessage,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 18),
),
const SizedBox(height: 16),
if (_isVerifying)
Text(
"Time remaining: $_countdown seconds",
style: const TextStyle(fontSize: 16, color: Colors.grey),
),
if (_verificationFailed && !_isVerifying) ...[
const SizedBox(height: 20),
ElevatedButton(
onPressed: _startVerificationProcess,
child: const Text('Retry'),
),
]
],
),
),
),
);
}
}

View File

@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:kmobile/data/models/beneficiary.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/widgets/bank_logos.dart';
@@ -7,39 +6,16 @@ import 'package:kmobile/api/services/beneficiary_service.dart';
import '../../../l10n/app_localizations.dart';
class BeneficiaryDetailsScreen extends StatefulWidget {
class BeneficiaryDetailsScreen extends StatelessWidget {
final Beneficiary beneficiary;
const BeneficiaryDetailsScreen({super.key, required this.beneficiary});
BeneficiaryDetailsScreen({super.key, required this.beneficiary});
@override
State<BeneficiaryDetailsScreen> createState() =>
_BeneficiaryDetailsScreenState();
}
class _BeneficiaryDetailsScreenState extends State<BeneficiaryDetailsScreen> {
final service = getIt<BeneficiaryService>();
late String _currentLimit;
final _limitController = TextEditingController();
@override
void initState() {
super.initState();
_currentLimit = (widget.beneficiary.transactionLimit == null ||
widget.beneficiary.transactionLimit!.isEmpty)
? 'N/A'
: widget.beneficiary.transactionLimit!;
}
@override
void dispose() {
_limitController.dispose();
super.dispose();
}
void _deleteBeneficiary(BuildContext context) async {
try {
await service.deleteBeneficiary(widget.beneficiary.accountNo);
await service.deleteBeneficiary(beneficiary.accountNo);
if (!context.mounted) {
return;
}
@@ -101,73 +77,6 @@ class _BeneficiaryDetailsScreenState extends State<BeneficiaryDetailsScreen> {
);
}
Future<void> _showEditLimitDialog() async {
_limitController.text = _currentLimit == 'N/A' ? '' : _currentLimit;
await showDialog(
context: context,
builder: (dialogContext) {
final localizations = AppLocalizations.of(dialogContext);
final theme = Theme.of(dialogContext);
return AlertDialog(
title: Text(localizations.editLimit),
content: TextField(
controller: _limitController,
autofocus: true,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d+')),
],
decoration: InputDecoration(
labelText: localizations.limitAmount,
prefixText: '',
border: const OutlineInputBorder(),
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(),
child: Text(localizations.cancel),
),
ElevatedButton(
onPressed: () async {
final value = _limitController.text;
if (value.isEmpty || int.tryParse(value) == null) return;
try {
await service.updateLimit(
beneficiaryAccountNo: widget.beneficiary.accountNo,
newLimit: value,
);
if (!mounted) return;
setState(() {
_currentLimit = value;
});
Navigator.of(dialogContext).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(localizations.limitUpdatedSuccess),
behavior: SnackBarBehavior.floating,
),
);
} catch (e) {
Navigator.of(dialogContext).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("${localizations.error}: $e"),
behavior: SnackBarBehavior.floating,
backgroundColor: theme.colorScheme.error,
),
);
}
},
child: Text(localizations.save),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -187,12 +96,11 @@ class _BeneficiaryDetailsScreenState extends State<BeneficiaryDetailsScreen> {
CircleAvatar(
radius: 24,
backgroundColor: Colors.transparent,
child:
getBankLogo(widget.beneficiary.bankName, context),
child: getBankLogo(beneficiary.bankName, context),
),
const SizedBox(width: 16),
Text(
widget.beneficiary.name,
beneficiary.name,
style: const TextStyle(
fontSize: 20, fontWeight: FontWeight.bold),
),
@@ -200,28 +108,28 @@ class _BeneficiaryDetailsScreenState extends State<BeneficiaryDetailsScreen> {
),
const SizedBox(height: 24),
_buildDetailRow('${AppLocalizations.of(context).bankName} ',
widget.beneficiary.bankName ?? 'N/A'),
beneficiary.bankName ?? 'N/A'),
_buildDetailRow(
'${AppLocalizations.of(context).accountNumber} ',
widget.beneficiary.accountNo),
beneficiary.accountNo),
_buildDetailRow(
'${AppLocalizations.of(context).accountType} ',
widget.beneficiary.accountType),
beneficiary.accountType),
_buildDetailRow('${AppLocalizations.of(context).ifscCode} ',
widget.beneficiary.ifscCode),
beneficiary.ifscCode),
_buildDetailRow('${AppLocalizations.of(context).branchName} ',
widget.beneficiary.branchName ?? 'N/A'),
_buildDetailRow(
"Beneficiary Transactional Limit", _currentLimit),
beneficiary.branchName ?? 'N/A'),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton.icon(
onPressed: _showEditLimitDialog,
icon: const Icon(Icons.currency_rupee),
label: Text(AppLocalizations.of(context).editLimit),
),
// ElevatedButton.icon(
// onPressed: () {
// // Set Transaction Limit for this beneficiary
// },
// icon: const Icon(Icons.currency_rupee),
// label: const Text('Set Limit'),
// ),
ElevatedButton.icon(
onPressed: () {
// Delete beneficiary option

View File

@@ -149,6 +149,7 @@ class _ManageBeneficiariesScreen extends State<ManageBeneficiariesScreen> {
decoration: InputDecoration(
hintText:
AppLocalizations.of(context).searchByNameOrAccountHint,
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),

View File

@@ -90,12 +90,12 @@ class CardTile extends StatelessWidget {
const Text(
"Kangra Central Co-operative Bank",
style: TextStyle(
color: Color.fromARGB(255, 238, 237, 237),
fontSize: 15,
color: Colors.white,
fontSize: 15.5,
fontWeight: FontWeight.bold,
),
overflow: TextOverflow.visible,
maxLines: 2,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
],
),

View File

@@ -19,6 +19,7 @@ class _CardManagementScreen extends State<CardManagementScreen> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(
AppLocalizations.of(context).cardManagement,
),
@@ -60,7 +61,7 @@ class _CardManagementScreen extends State<CardManagementScreen> {
),
);
},
disabled: false,
disabled: true,
),
Divider(height: 1, color: Theme.of(context).dividerColor),
CardManagementTile(

View File

@@ -45,7 +45,7 @@ class _CardPinChangeDetailsScreen extends State<CardPinChangeDetailsScreen> {
return Scaffold(
appBar: AppBar(
title: Text(
AppLocalizations.of(context).changeCardPin,
AppLocalizations.of(context).cardDetails,
),
centerTitle: false,
),
@@ -66,8 +66,12 @@ class _CardPinChangeDetailsScreen extends State<CardPinChangeDetailsScreen> {
isDense: true,
filled: true,
fillColor: Theme.of(context).scaffoldBackgroundColor,
enabledBorder: const OutlineInputBorder(),
focusedBorder: const OutlineInputBorder(),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.black, width: 2),
),
),
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
@@ -88,8 +92,13 @@ class _CardPinChangeDetailsScreen extends State<CardPinChangeDetailsScreen> {
filled: true,
fillColor:
Theme.of(context).scaffoldBackgroundColor,
enabledBorder: const OutlineInputBorder(),
focusedBorder: const OutlineInputBorder(),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
focusedBorder: const OutlineInputBorder(
borderSide:
BorderSide(color: Colors.black, width: 2),
),
),
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
@@ -114,8 +123,13 @@ class _CardPinChangeDetailsScreen extends State<CardPinChangeDetailsScreen> {
filled: true,
fillColor:
Theme.of(context).scaffoldBackgroundColor,
enabledBorder: const OutlineInputBorder(),
focusedBorder: const OutlineInputBorder(),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
focusedBorder: const OutlineInputBorder(
borderSide:
BorderSide(color: Colors.black, width: 2),
),
),
validator: (value) => value != null &&
value.isNotEmpty
@@ -135,8 +149,12 @@ class _CardPinChangeDetailsScreen extends State<CardPinChangeDetailsScreen> {
isDense: true,
filled: true,
fillColor: Theme.of(context).scaffoldBackgroundColor,
enabledBorder: const OutlineInputBorder(),
focusedBorder: const OutlineInputBorder(),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.black, width: 2),
),
),
textInputAction: TextInputAction.done,
keyboardType: TextInputType.phone,

View File

@@ -1,8 +1,6 @@
import 'package:flutter/material.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/features/cheque/screens/cheque_enquiry_screen.dart';
import 'package:kmobile/features/cheque/screens/positive_pay_screen.dart';
import 'package:kmobile/features/cheque/screens/revoke_stop_screen.dart';
import 'package:kmobile/features/cheque/screens/stop_cheque_screen.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import '../../../l10n/app_localizations.dart';
@@ -44,6 +42,8 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
child: ChequeManagementCardTile(
icon: Symbols.payments,
label: AppLocalizations.of(context).chequeEnquiryTitle,
subtitle:
AppLocalizations.of(context).chequeEnquirySubtitle,
onTap: () {
Navigator.push(
context,
@@ -61,6 +61,7 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
child: ChequeManagementCardTile(
icon: Symbols.block_sharp,
label: AppLocalizations.of(context).stopCheque,
subtitle: AppLocalizations.of(context).stopChequeSubtitle,
onTap: () {
Navigator.push(
context,
@@ -74,41 +75,6 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
},
),
),
Expanded(
child: ChequeManagementCardTile(
icon: Symbols.stop_circle,
label: AppLocalizations.of(context).revokeStop,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RevokeStopChequeScreen(
users: users,
selectedIndex: selectedAccountIndex,
),
),
);
},
),
),
Expanded(
child: ChequeManagementCardTile(
icon: Symbols
.check_circle, // Using check_circle for Positive Pay
label: AppLocalizations.of(context).positivePayTitle,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PositivePayScreen(
users: users,
selectedIndex: selectedAccountIndex,
),
),
);
},
),
),
],
),
),
@@ -135,6 +101,7 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
class ChequeManagementCardTile extends StatelessWidget {
final IconData icon;
final String label;
final String? subtitle;
final VoidCallback onTap;
final bool disable;
@@ -142,6 +109,7 @@ class ChequeManagementCardTile extends StatelessWidget {
super.key,
required this.icon,
required this.label,
this.subtitle,
required this.onTap,
this.disable = false,
});
@@ -184,6 +152,19 @@ class ChequeManagementCardTile extends StatelessWidget {
: theme.colorScheme.onSurface,
),
),
if (subtitle != null)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
subtitle!,
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium?.copyWith(
color: disable
? theme.disabledColor
: theme.colorScheme.onSurfaceVariant,
),
),
),
],
),
),

View File

@@ -1,339 +0,0 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/cheque_service.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/features/fund_transfer/screens/transaction_pin_screen.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class PositivePayScreen extends StatefulWidget {
final List<User> users;
final int selectedIndex;
const PositivePayScreen({
super.key,
required this.users,
required this.selectedIndex,
});
@override
State<PositivePayScreen> createState() => _PositivePayScreenState();
}
class _PositivePayScreenState extends State<PositivePayScreen> {
User? _selectedAccount;
List<User> _filteredUsers = [];
final _formKey = GlobalKey<FormState>();
final _chequeNumberController = TextEditingController();
final _chequeDateController = TextEditingController();
final _amountController = TextEditingController();
final _payeeController = TextEditingController();
final _chequeService = getIt<ChequeService>();
String? _ciFromCheque;
String? _ciToCheque;
@override
void initState() {
super.initState();
_filteredUsers = widget.users
.where((user) => ['SA', 'SB', 'CA', 'CC'].contains(user.accountType))
.toList();
// Pre-fill the account number if possible
if (widget.users.isNotEmpty && widget.selectedIndex < widget.users.length) {
if (_filteredUsers.isNotEmpty) {
if (_filteredUsers.contains(widget.users[widget.selectedIndex])) {
_selectedAccount = widget.users[widget.selectedIndex];
} else {
_selectedAccount = _filteredUsers.first;
}
} else {
_selectedAccount = widget.users[widget.selectedIndex];
}
} else {
if (_filteredUsers.isNotEmpty) {
_selectedAccount = _filteredUsers.first;
}
}
_loadChequeDetails();
}
Future<void> _loadChequeDetails() async {
if (_selectedAccount == null) return;
String instrType;
switch (_selectedAccount!.accountType) {
case 'SA':
case 'SB':
instrType = '10';
break;
case 'CA':
instrType = '11';
break;
case 'CC':
instrType = '13';
break;
default:
instrType = '10';
}
try {
final data = await _chequeService.ChequeEnquiry(
accountNumber: _selectedAccount!.accountNo!,
instrType: instrType,
);
final ciCheque = data.where((cheque) => cheque.type == 'CI').toList();
if (mounted) {
setState(() {
if (ciCheque.isNotEmpty) {
_ciFromCheque = ciCheque.first.fromCheque;
_ciToCheque = ciCheque.first.toCheque;
} else {
_ciFromCheque = null;
_ciToCheque = null;
}
});
}
} catch (e) {
if (mounted) {
setState(() {
_ciFromCheque = null;
_ciToCheque = null;
});
}
}
}
@override
void dispose() {
_chequeNumberController.dispose();
_chequeDateController.dispose();
_amountController.dispose();
_payeeController.dispose();
super.dispose();
}
Future<void> _showResponseDialog(String title, String message) async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text(title),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text(message),
],
),
),
actions: <Widget>[
TextButton(
child: Text(AppLocalizations.of(context).closeButton),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
AppLocalizations.of(context)
.positivePay, // Will be replaced by localization
),
centerTitle: false,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
const SizedBox(height: 16.0),
DropdownButtonFormField<User>(
value: _selectedAccount,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).accountNumber,
border: const OutlineInputBorder(),
),
items: _filteredUsers.map((user) {
return DropdownMenuItem<User>(
value: user,
child: Text(user.accountNo.toString()),
);
}).toList(),
onChanged: (User? newUser) {
setState(() {
_selectedAccount = newUser;
_loadChequeDetails();
});
},
validator: (value) {
if (value == null) {
return AppLocalizations.of(context).accountNumberRequired;
}
return null;
},
),
const SizedBox(height: 16.0),
TextFormField(
controller: _chequeNumberController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).chequeNumberLabel,
border: const OutlineInputBorder(),
errorMaxLines: 2,
),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return AppLocalizations.of(context).pleaseEnterChequeNumber;
}
final chequeNumber = int.tryParse(value);
final fromCheque = int.tryParse(_ciFromCheque ?? '');
final toCheque = int.tryParse(_ciToCheque ?? '');
if (chequeNumber == null) {
return AppLocalizations.of(context)
.invalidChequeNumberFormatError;
}
if (fromCheque != null && toCheque != null) {
if (chequeNumber < fromCheque || chequeNumber > toCheque) {
return AppLocalizations.of(context)
.chequeNumberRangeError(_ciFromCheque!, _ciToCheque!);
}
}
return null;
},
),
const SizedBox(height: 16.0),
TextFormField(
controller: _chequeDateController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).chequeIssuedDate,
border: const OutlineInputBorder(),
suffixIcon: const Icon(Icons.calendar_today),
),
readOnly: true,
onTap: () async {
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime.now(),
);
if (pickedDate != null) {
// Format the date as you wish
String formattedDate =
"${pickedDate.year}-${pickedDate.month.toString().padLeft(2, '0')}-${pickedDate.day.toString().padLeft(2, '0')}";
_chequeDateController.text = formattedDate;
}
},
validator: (value) {
if (value == null || value.isEmpty) {
return AppLocalizations.of(context)
.pleaseSelectChequeIssuedDate;
}
return null;
},
),
const SizedBox(height: 16.0),
TextFormField(
controller: _payeeController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).payeeName,
border: const OutlineInputBorder(),
),
),
const SizedBox(height: 16.0),
TextFormField(
controller: _amountController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).amountLabel,
border: const OutlineInputBorder(),
prefixText: '',
),
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
validator: (value) {
if (value == null || value.isEmpty) {
return AppLocalizations.of(context).pleaseEnterAmount;
}
return null;
},
),
const SizedBox(height: 32.0),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Process data
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TransactionPinScreen(
onPinCompleted: (ctx, pin) async {
Navigator.pop(context);
try {
final response = await _chequeService.registerPPS(
cheque_no: _chequeNumberController.text,
account_number: _selectedAccount!.accountNo!,
issue_date: _chequeDateController.text,
amount: _amountController.text,
payee_name: _payeeController.text,
tpin: pin,
);
if (!mounted) return;
String responseString = response.toString();
if (responseString ==
'PPS Registered Successfully') {
_showResponseDialog('REGISTRATION SUCCESFUL',
'Your Positive Pay Request has been registered succesfully');
}
if (responseString
.contains('Cheque already registered')) {
_showResponseDialog('ERROR',
'Your Request for the selected cheque number has already been registered');
}
if (responseString.contains('INCORRECT_TPIN')) {
_showResponseDialog('Invalid TPIN',
'The TPIN you entered is incorrect. Please try again.');
}
} on DioException catch (e) {
try {
final errorBodyString =
e.toString().split('Exception: ')[1];
final errorBody = jsonDecode(errorBodyString);
if (errorBody.containsKey('error') &&
errorBody['error'] == 'INCORRECT_TPIN') {
_showResponseDialog('Invalid TPIN',
'The TPIN you entered is incorrect. Please try again.');
} else {
_showResponseDialog(
'Error', 'Internal Server Error');
}
} catch (_) {
_showResponseDialog(
'Error', 'Internal Server Error');
}
}
},
),
),
);
}
},
child: Text(AppLocalizations.of(context).proceedButton),
),
],
),
),
),
);
}
}

View File

@@ -1,344 +0,0 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/cheque_service.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/features/fund_transfer/screens/transaction_pin_screen.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class RevokeStopMultipleChequesScreen extends StatefulWidget {
final User selectedAccount;
final String date;
final String instrType;
final String fromCheque;
final String toCheque;
const RevokeStopMultipleChequesScreen(
{super.key,
required this.selectedAccount,
required this.date,
required this.instrType,
required this.fromCheque,
required this.toCheque});
@override
State<RevokeStopMultipleChequesScreen> createState() =>
_RevokeStopMultipleChequesScreenState();
}
class _RevokeStopMultipleChequesScreenState
extends State<RevokeStopMultipleChequesScreen> {
final _formKey = GlobalKey<FormState>();
final _stopFromChequeNoController = TextEditingController();
final _stopToChequeNoController = TextEditingController();
final _stopIssueDateController = TextEditingController();
final _stopExpiryDateController = TextEditingController();
final _stopAmountController = TextEditingController();
final _chequeService = getIt<ChequeService>();
String? _selectedComment;
final _otherCommentController = TextEditingController();
bool _showOtherCommentField = false;
final List<String> _commentOptions = [
'Cheque Found',
'Cheque Fixed',
'Other'
];
Future<void> _selectDate(TextEditingController controller) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime(2101),
);
if (picked != null) {
setState(() {
controller.text =
'${picked.day.toString().padLeft(2, '0')}/${picked.month.toString().padLeft(2, '0')}/${picked.year}';
});
}
}
Future<void> _showResponseDialog(String title, String message) async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text(title),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text(message),
],
),
),
actions: <Widget>[
TextButton(
child: const Text('Close'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context).revokeMultipleStops),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: ListView(
children: [
Card(
elevation: 0,
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: ListTile(
leading: Image.asset(
'assets/images/logo.png',
width: 40,
height: 40,
),
title: Text(widget.selectedAccount.accountNo!),
subtitle:
Text(AppLocalizations.of(context).accountNumberTitle),
),
),
const SizedBox(height: 24),
TextFormField(
controller: _stopFromChequeNoController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).fromChequeNumberHint,
border: const OutlineInputBorder(),
errorMaxLines: 2,
),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return AppLocalizations.of(context)
.pleaseEnterChequeNumberError;
}
final chequeNumber = int.tryParse(value);
final fromCheque = int.tryParse(widget.fromCheque);
final toCheque = int.tryParse(widget.toCheque);
if (chequeNumber == null ||
fromCheque == null ||
toCheque == null) {
return AppLocalizations.of(context)
.invalidChequeNumberFormatError;
}
if (chequeNumber < fromCheque || chequeNumber > toCheque) {
return AppLocalizations.of(context).chequeNumberRangeError(
widget.fromCheque, widget.toCheque);
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _stopToChequeNoController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).toChequeNumberHint,
border: const OutlineInputBorder(),
errorMaxLines: 2,
),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return AppLocalizations.of(context)
.pleaseEnterChequeNumberError;
}
final chequeNumber = int.tryParse(value);
final fromCheque = int.tryParse(widget.fromCheque);
final toCheque = int.tryParse(widget.toCheque);
if (chequeNumber == null ||
fromCheque == null ||
toCheque == null) {
return AppLocalizations.of(context)
.invalidChequeNumberFormatError;
}
if (chequeNumber < fromCheque || chequeNumber > toCheque) {
return AppLocalizations.of(context).chequeNumberRangeError(
widget.fromCheque, widget.toCheque);
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
initialValue: widget.instrType,
readOnly: true,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).instrumentTypeLabel,
border: const OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextFormField(
controller: _stopIssueDateController,
readOnly: true,
onTap: () => _selectDate(_stopIssueDateController),
decoration: InputDecoration(
labelText: AppLocalizations.of(context).revokeIssueDate,
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.calendar_today),
onPressed: () => _selectDate(_stopIssueDateController),
),
),
keyboardType: TextInputType.datetime,
),
const SizedBox(height: 16),
TextFormField(
controller: _stopExpiryDateController,
readOnly: true,
onTap: () => _selectDate(_stopExpiryDateController),
decoration: InputDecoration(
labelText: AppLocalizations.of(context).revokeExpiryDate,
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.calendar_today),
onPressed: () => _selectDate(_stopExpiryDateController),
),
),
keyboardType: TextInputType.datetime,
),
const SizedBox(height: 16),
TextFormField(
controller: _stopAmountController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).revokeAmount,
prefixText: '',
border: const OutlineInputBorder(),
),
keyboardType: TextInputType.number,
),
const SizedBox(height: 16),
DropdownButtonFormField<String>(
value: _selectedComment,
items: _commentOptions.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (newValue) {
setState(() {
_selectedComment = newValue;
_showOtherCommentField = newValue == 'Other';
});
},
decoration: InputDecoration(
labelText: AppLocalizations.of(context).revokeComment,
border: const OutlineInputBorder(),
),
),
if (_showOtherCommentField)
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: TextFormField(
controller: _otherCommentController,
decoration: const InputDecoration(
labelText: "Other Reasons :",
border: OutlineInputBorder(),
),
validator: (value) {
return null;
},
),
),
const SizedBox(height: 32),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TransactionPinScreen(
onPinCompleted: (ctx, pin) async {
Navigator.pop(context);
try {
final response = await _chequeService.revokeStop(
accountno: widget.selectedAccount.accountNo!,
removeFromChequeNo:
_stopFromChequeNoController.text,
instrType: widget.instrType,
removeToChequeNo:
_stopToChequeNoController.text,
removeIssueDate: _stopIssueDateController.text,
removeExpiryDate:
_stopExpiryDateController.text,
removeAmount: _stopAmountController.text,
removeComment: _selectedComment == 'Other'
? _otherCommentController.text
: _selectedComment ?? '',
tpin: pin,
);
if (!mounted) return;
final decodedResponse = jsonDecode(response);
String responseString = response
.toString(); // used as the case only for incorrect TPIN
final status = decodedResponse['status'];
final message = decodedResponse['message'];
final code = decodedResponse['code'];
if (status == 'SUCCESS') {
_showResponseDialog('Success', message);
}
if (status == 'ERROR') {
String errMessage = "error";
if (code == '0172') {
errMessage =
'The selected Cheque is not stopped';
} else if (code == '0748') {
errMessage =
'The selected Cheque is already presented';
}
_showResponseDialog('Error', errMessage);
}
if (responseString.contains('INCORRECT_TPIN')) {
_showResponseDialog('Invalid TPIN',
'The TPIN you entered is incorrect. Please try again.');
}
} on Exception catch (e) {
try {
final errorBodyString =
e.toString().split('Exception: ')[1];
final errorBody = jsonDecode(errorBodyString);
if (errorBody.containsKey('error') &&
errorBody['error'] == 'INCORRECT_TPIN') {
_showResponseDialog('Invalid TPIN',
'The TPIN you entered is incorrect. Please try again.');
} else {
_showResponseDialog(
'Error', 'Internal Server Error');
}
} catch (_) {
_showResponseDialog(
'Error', 'Internal Server Error');
}
}
},
),
),
);
}
},
child: Text(AppLocalizations.of(context).revokeStopButton),
),
],
),
),
),
);
}
}

View File

@@ -1,365 +0,0 @@
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/di/injection.dart';
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/cheque_service.dart';
import 'package:kmobile/features/cheque/screens/revoke%20_stop_multiple_screen.dart';
import 'package:kmobile/features/cheque/screens/revoke_stop_single_screen.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class RevokeStopChequeScreen extends StatefulWidget {
final List<User> users;
final int selectedIndex;
const RevokeStopChequeScreen({
super.key,
required this.users,
required this.selectedIndex,
});
@override
State<RevokeStopChequeScreen> createState() => _RevokeStopChequeScreenState();
}
class _RevokeStopChequeScreenState extends State<RevokeStopChequeScreen> {
User? _selectedAccount;
var service = getIt<ChequeService>();
bool _isLoading = true;
List<Cheque> _stCheques = [];
List<User> _filteredUsers = [];
String? _ciFromCheque;
String? _ciToCheque;
@override
void initState() {
super.initState();
_filteredUsers = widget.users
.where((user) => ['SA', 'SB', 'CA', 'CC'].contains(user.accountType))
.toList();
if (widget.users.isNotEmpty && widget.selectedIndex < widget.users.length) {
if (_filteredUsers.isNotEmpty) {
if (_filteredUsers.contains(widget.users[widget.selectedIndex])) {
_selectedAccount = widget.users[widget.selectedIndex];
} else {
_selectedAccount = _filteredUsers.first;
}
} else {
_selectedAccount = widget.users[widget.selectedIndex];
}
} else {
if (_filteredUsers.isNotEmpty) {
_selectedAccount = _filteredUsers.first;
}
}
_loadCheques();
}
Future<void> _loadCheques() async {
if (_selectedAccount == null) {
setState(() {
_isLoading = false;
_stCheques = [];
});
return;
}
setState(() {
_isLoading = true;
});
String instrType;
switch (_selectedAccount!.accountType) {
case 'SA':
case 'SB':
instrType = '10';
break;
case 'CA':
instrType = '11';
break;
case 'CC':
instrType = '13';
break;
default:
instrType = '10';
}
try {
final data = await service.ChequeEnquiry(
accountNumber: _selectedAccount!.accountNo!, instrType: instrType);
final stCheques = data.where((cheque) => cheque.type == 'ST').toList();
final ciCheque = data.where((cheque) => cheque.type == 'CI').toList();
setState(() {
_stCheques = stCheques;
if (ciCheque.isNotEmpty) {
_ciFromCheque = ciCheque.first.fromCheque;
_ciToCheque = ciCheque.first.toCheque;
} else {
_ciFromCheque = null;
_ciToCheque = null;
}
_isLoading = false;
});
} catch (e) {
setState(() {
_isLoading = false;
_stCheques = [];
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to fetch cheque status: ${e.toString()}'),
),
);
}
}
String _getAccountTypeDisplayName(String accountType) {
switch (accountType.toLowerCase()) {
case 'sa':
return AppLocalizations.of(context).savingsAccount;
case 'sb':
return AppLocalizations.of(context).savingsAccount;
case 'ca':
return "Current Account";
case 'cc':
return "Cash Credit Account";
default:
return accountType;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context).revokeStop),
centerTitle: false,
),
body: Stack(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(children: [
Card(
elevation: 4,
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
AppLocalizations.of(context).accountNumber,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 18),
),
const SizedBox(width: 16),
if (_selectedAccount != null)
Expanded(
child: DropdownButton<User>(
value: _selectedAccount,
onChanged: (User? newUser) {
if (newUser != null) {
setState(() {
_selectedAccount = newUser;
_loadCheques();
});
}
},
items: _filteredUsers.map((user) {
return DropdownMenuItem<User>(
value: user,
child: Text(user.accountNo.toString()),
);
}).toList(),
),
)
else
Text(AppLocalizations.of(context).noAccountsFound),
],
),
),
),
const SizedBox(height: 20),
Row(
children: [
Expanded(
child: Card(
color: Theme.of(context).colorScheme.primaryContainer,
elevation: 4,
child: InkWell(
onTap: () {
if (_selectedAccount != null &&
_stCheques.isNotEmpty) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
RevokeStopSingleChequeScreen(
selectedAccount: _selectedAccount!,
date: _stCheques.first.Date!,
instrType: _stCheques.first.InstrType!,
fromCheque: _ciFromCheque ??
_stCheques.first.fromCheque!,
toCheque:
_ciToCheque ?? _stCheques.first.toCheque!,
),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("No stopped cheques present"),
),
);
}
},
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Text(
AppLocalizations.of(context)
.revokeSingleStopTitle,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
),
),
),
),
),
),
const SizedBox(width: 10),
Expanded(
child: Card(
color: Theme.of(context).colorScheme.primaryContainer,
elevation: 4,
child: InkWell(
onTap: () {
if (_selectedAccount != null &&
_stCheques.isNotEmpty) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
RevokeStopMultipleChequesScreen(
selectedAccount: _selectedAccount!,
date: _stCheques.first.Date!,
instrType: _stCheques.first.InstrType!,
fromCheque: _ciFromCheque ??
_stCheques.first.fromCheque!,
toCheque:
_ciToCheque ?? _stCheques.first.toCheque!,
),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)
.pleaseSelectAccountFirst),
),
);
}
},
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Text(
AppLocalizations.of(context).revokeMultipleStops,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.colorScheme
.onSecondaryContainer,
),
),
),
),
),
),
),
],
),
const SizedBox(height: 20),
Expanded(
child: _isLoading
? const Center(child: CircularProgressIndicator())
: _stCheques.isEmpty
? Center(
child: Text(AppLocalizations.of(context)
.noChequeIssuedStatus))
: ListView.builder(
itemCount: _stCheques.length,
itemBuilder: (context, index) {
return _buildSTTile(context, _stCheques[index]);
},
),
),
]),
),
IgnorePointer(
child: Center(
child: Opacity(
opacity: 0.07, // Reduced opacity
child: ClipOval(
child: Image.asset(
'assets/images/logo.png',
width: 200, // Adjust size as needed
height: 200, // Adjust size as needed
),
),
),
),
),
],
),
);
}
Widget _buildSTTile(BuildContext context, Cheque cheque) {
return Card(
margin: const EdgeInsets.symmetric(
vertical: 8.0,
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(AppLocalizations.of(context).stopChequeLabel,
style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 8),
_buildInfoRow('From Cheque:', cheque.fromCheque),
_buildInfoRow('To Cheque:', cheque.toCheque),
_buildInfoRow('Account Type:',
_getAccountTypeDisplayName(_selectedAccount!.accountType!)),
_buildInfoRow('Branch Code:', cheque.branchCode),
_buildInfoRow('Stop Issue Date:', cheque.stopIssueDate),
_buildInfoRow('Stop Expiry Date:', cheque.StopExpiryDate),
_buildInfoRow('Cheques Count:', cheque.Chequescount),
],
),
),
);
}
Widget _buildInfoRow(String label, String? value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(fontWeight: FontWeight.bold)),
Text(value ?? ''),
],
),
);
}
}

View File

@@ -1,311 +0,0 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/di/injection.dart';
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/cheque_service.dart';
import 'package:kmobile/features/fund_transfer/screens/transaction_pin_screen.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class RevokeStopSingleChequeScreen extends StatefulWidget {
final User selectedAccount;
final String date;
final String instrType;
final String fromCheque;
final String toCheque;
const RevokeStopSingleChequeScreen(
{super.key,
required this.selectedAccount,
required this.date,
required this.instrType,
required this.fromCheque,
required this.toCheque});
@override
State<RevokeStopSingleChequeScreen> createState() =>
_RevokeStopSingleChequeScreenState();
}
class _RevokeStopSingleChequeScreenState
extends State<RevokeStopSingleChequeScreen> {
final _formKey = GlobalKey<FormState>();
final _stopFromChequeNoController = TextEditingController();
final _stopIssueDateController = TextEditingController();
final _stopExpiryDateController = TextEditingController();
final _stopAmountController = TextEditingController();
final _chequeService = getIt<ChequeService>();
String? _selectedComment;
final _otherCommentController = TextEditingController();
bool _showOtherCommentField = false;
final List<String> _commentOptions = [
'Cheque Found',
'Cheque Fixed',
'Other'
];
Future<void> _selectDate(TextEditingController controller) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime(2101),
);
if (picked != null) {
setState(() {
controller.text =
'${picked.day.toString().padLeft(2, '0')}/${picked.month.toString().padLeft(2, '0')}/${picked.year}';
});
}
}
Future<void> _showResponseDialog(String title, String message) async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text(title),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text(message),
],
),
),
actions: <Widget>[
TextButton(
child: Text(AppLocalizations.of(context).closeButton),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context).revokeSingleStopTitle)),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: ListView(
children: [
Card(
elevation: 0,
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: ListTile(
leading: Image.asset(
'assets/images/logo.png',
width: 40,
height: 40,
),
title: Text(widget.selectedAccount.accountNo!),
subtitle:
Text(AppLocalizations.of(context).accountNumberLabel),
),
),
const SizedBox(height: 24),
TextFormField(
controller: _stopFromChequeNoController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).chequeNumberLabel,
border: const OutlineInputBorder(),
errorMaxLines: 2,
),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return AppLocalizations.of(context)
.pleaseEnterChequeNumberError;
}
final chequeNumber = int.tryParse(value);
final fromCheque = int.tryParse(widget.fromCheque);
final toCheque = int.tryParse(widget.toCheque);
if (chequeNumber == null ||
fromCheque == null ||
toCheque == null) {
return AppLocalizations.of(context)
.invalidChequeNumberFormatError;
}
if (chequeNumber < fromCheque || chequeNumber > toCheque) {
return AppLocalizations.of(context).chequeNumberRangeError(
widget.fromCheque, widget.toCheque);
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
initialValue: widget.instrType,
readOnly: true,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).instrumentTypeLabel,
border: const OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextFormField(
controller: _stopIssueDateController,
readOnly: true,
onTap: () => _selectDate(_stopIssueDateController),
decoration: InputDecoration(
labelText: AppLocalizations.of(context).revokeIssueDate,
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.calendar_today),
onPressed: () => _selectDate(_stopIssueDateController),
),
),
keyboardType: TextInputType.datetime,
),
const SizedBox(height: 16),
TextFormField(
controller: _stopExpiryDateController,
readOnly: true,
onTap: () => _selectDate(_stopExpiryDateController),
decoration: InputDecoration(
labelText: AppLocalizations.of(context).revokeExpiryDate,
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.calendar_today),
onPressed: () => _selectDate(_stopExpiryDateController),
),
),
keyboardType: TextInputType.datetime,
),
const SizedBox(height: 16),
TextFormField(
controller: _stopAmountController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).revokeAmount,
border: const OutlineInputBorder(),
),
keyboardType: TextInputType.number,
),
const SizedBox(height: 16),
DropdownButtonFormField<String>(
value: _selectedComment,
items: _commentOptions.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (newValue) {
setState(() {
_selectedComment = newValue;
_showOtherCommentField = newValue == 'Other';
});
},
decoration: InputDecoration(
labelText: AppLocalizations.of(context).revokeComment,
border: const OutlineInputBorder(),
),
),
if (_showOtherCommentField)
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: TextFormField(
controller: _otherCommentController,
decoration: const InputDecoration(
labelText: "Other Reasons :",
border: OutlineInputBorder(),
),
validator: (value) {
return null;
},
),
),
const SizedBox(height: 32),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TransactionPinScreen(
onPinCompleted: (ctx, pin) async {
Navigator.pop(context);
try {
final response = await _chequeService.revokeStop(
accountno: widget.selectedAccount.accountNo!,
removeFromChequeNo:
_stopFromChequeNoController.text,
instrType: widget.instrType,
removeToChequeNo:
_stopFromChequeNoController.text,
removeIssueDate: _stopIssueDateController.text,
removeExpiryDate:
_stopExpiryDateController.text,
removeAmount: _stopAmountController.text,
removeComment: _selectedComment == 'Other'
? _otherCommentController.text
: _selectedComment ?? '',
tpin: pin,
);
if (!mounted) return;
final decodedResponse = jsonDecode(response);
String responseString = response
.toString(); // used as the case only for incorrect TPIN
final status = decodedResponse['status'];
final message = decodedResponse['message'];
final code = decodedResponse['code'];
if (status == 'SUCCESS') {
_showResponseDialog('Success', message);
}
if (status == 'ERROR') {
String errMessage = "error";
if (code == '0172') {
errMessage =
'The selected Cheque is not stopped';
} else if (code == '0748') {
errMessage =
'The selected Cheque is already presented';
}
_showResponseDialog('Error', errMessage);
}
if (responseString.contains('INCORRECT_TPIN')) {
_showResponseDialog('Invalid TPIN',
'The TPIN you entered is incorrect. Please try again.');
}
} on DioException catch (e) {
try {
final errorBodyString =
e.toString().split('Exception: ')[1];
final errorBody = jsonDecode(errorBodyString);
if (errorBody.containsKey('error') &&
errorBody['error'] == 'INCORRECT_TPIN') {
_showResponseDialog('Invalid TPIN',
'The TPIN you entered is incorrect. Please try again.');
} else {
_showResponseDialog(
'Error', 'Internal Server Error');
}
} catch (_) {
_showResponseDialog(
'Error', 'Internal Server Error');
}
}
},
),
),
);
}
},
child: Text(AppLocalizations.of(context).revokeStopButton),
),
],
),
),
),
);
}
}

View File

@@ -34,19 +34,9 @@ class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
final _stopIssueDateController = TextEditingController();
final _stopExpiryDateController = TextEditingController();
final _stopAmountController = TextEditingController();
final _stopCommentController = TextEditingController();
final _chequeService = getIt<ChequeService>();
String? _selectedComment;
final _otherCommentController = TextEditingController();
bool _showOtherCommentField = false;
final List<String> _commentOptions = [
'Cheque Lost',
'Cheque Stolen',
'Cheque Missing',
'Cheque Damaged',
'Other'
];
String _formatDate(String dateString) {
if (dateString.length != 8) {
return dateString; // Return as is if not in expected ddmmyyyy format
@@ -61,21 +51,6 @@ class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
}
}
Future<void> _selectDate(TextEditingController controller) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime(2101),
);
if (picked != null) {
setState(() {
controller.text =
'${picked.day.toString().padLeft(2, '0')}/${picked.month.toString().padLeft(2, '0')}/${picked.year}';
});
}
}
Future<void> _showResponseDialog(String title, String message) async {
return showDialog<void>(
context: context,
@@ -135,7 +110,6 @@ class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
decoration: InputDecoration(
labelText: AppLocalizations.of(context).fromChequeNumberHint,
border: const OutlineInputBorder(),
errorMaxLines: 2,
),
keyboardType: TextInputType.number,
validator: (value) {
@@ -165,7 +139,6 @@ class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
decoration: InputDecoration(
labelText: AppLocalizations.of(context).toChequeNumberHint,
border: const OutlineInputBorder(),
errorMaxLines: 2,
),
keyboardType: TextInputType.number,
validator: (value) {
@@ -201,30 +174,18 @@ class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
const SizedBox(height: 16),
TextFormField(
controller: _stopIssueDateController,
readOnly: true,
onTap: () => _selectDate(_stopIssueDateController),
decoration: InputDecoration(
labelText: AppLocalizations.of(context).stopIssueDateHint,
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.calendar_today),
onPressed: () => _selectDate(_stopIssueDateController),
),
),
keyboardType: TextInputType.datetime,
),
const SizedBox(height: 16),
TextFormField(
controller: _stopExpiryDateController,
readOnly: true,
onTap: () => _selectDate(_stopExpiryDateController),
decoration: InputDecoration(
labelText: AppLocalizations.of(context).stopExpiryDateHint,
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.calendar_today),
onPressed: () => _selectDate(_stopExpiryDateController),
),
),
keyboardType: TextInputType.datetime,
),
@@ -238,39 +199,13 @@ class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
keyboardType: TextInputType.number,
),
const SizedBox(height: 16),
DropdownButtonFormField<String>(
value: _selectedComment,
items: _commentOptions.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (newValue) {
setState(() {
_selectedComment = newValue;
_showOtherCommentField = newValue == 'Other';
});
},
TextFormField(
controller: _stopCommentController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).stopCommentHint,
border: const OutlineInputBorder(),
),
),
if (_showOtherCommentField)
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: TextFormField(
controller: _otherCommentController,
decoration: const InputDecoration(
labelText: "Other Reasons :",
border: OutlineInputBorder(),
),
validator: (value) {
return null;
},
),
),
const SizedBox(height: 16),
TextFormField(
initialValue: _formatDate(widget.date),
@@ -301,38 +236,23 @@ class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
stopIssueDate: _stopIssueDateController.text,
stopExpiryDate: _stopExpiryDateController.text,
stopAmount: _stopAmountController.text,
stopComment: _selectedComment == 'Other'
? _otherCommentController.text
: _selectedComment ?? '',
stopComment: _stopCommentController.text,
chequeIssueDate: widget.date,
tpin: pin,
);
if (!mounted) return;
final decodedResponse = jsonDecode(response);
String responseString = response
.toString(); // used as the case only for incorrect TPIN
final status = decodedResponse['status'];
final message = decodedResponse['message'];
final code = decodedResponse['code'];
if (status == 'SUCCESS') {
_showResponseDialog('Success', message);
}
if (status == 'ERROR') {
String errMessage = "error";
if (code == '0429') {
errMessage =
'The selected Cheque is already stopped';
} else if (code == '0748') {
errMessage =
'The selected Cheque is already presented';
}
_showResponseDialog('Error', errMessage);
}
if (responseString.contains('INCORRECT_TPIN')) {
_showResponseDialog('Invalid TPIN',
'The TPIN you entered is incorrect. Please try again.');
} else {
_showResponseDialog('Error', message);
}
} on Exception catch (e) {
print('inside catch block');
print(e.toString());
try {
final errorBodyString =
e.toString().split('Exception: ')[1];

View File

@@ -1,5 +1,4 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/di/injection.dart';
import 'package:flutter/material.dart';
@@ -32,19 +31,9 @@ class _StopSingleChequeScreenState extends State<StopSingleChequeScreen> {
final _stopIssueDateController = TextEditingController();
final _stopExpiryDateController = TextEditingController();
final _stopAmountController = TextEditingController();
final _stopCommentController = TextEditingController();
final _chequeService = getIt<ChequeService>();
String? _selectedComment;
final _otherCommentController = TextEditingController();
bool _showOtherCommentField = false;
final List<String> _commentOptions = [
'Cheque Lost',
'Cheque Stolen',
'Cheque Missing',
'Cheque Damaged',
'Other'
];
String _formatDate(String dateString) {
if (dateString.length != 8) {
return dateString; // Return as is if not in expected ddmmyyyy format
@@ -59,21 +48,6 @@ class _StopSingleChequeScreenState extends State<StopSingleChequeScreen> {
}
}
Future<void> _selectDate(TextEditingController controller) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime(2101),
);
if (picked != null) {
setState(() {
controller.text =
'${picked.day.toString().padLeft(2, '0')}/${picked.month.toString().padLeft(2, '0')}/${picked.year}';
});
}
}
Future<void> _showResponseDialog(String title, String message) async {
return showDialog<void>(
context: context,
@@ -133,7 +107,6 @@ class _StopSingleChequeScreenState extends State<StopSingleChequeScreen> {
decoration: InputDecoration(
labelText: AppLocalizations.of(context).chequeNumberLabel,
border: OutlineInputBorder(),
errorMaxLines: 2,
),
keyboardType: TextInputType.number,
validator: (value) {
@@ -169,30 +142,18 @@ class _StopSingleChequeScreenState extends State<StopSingleChequeScreen> {
const SizedBox(height: 16),
TextFormField(
controller: _stopIssueDateController,
readOnly: true,
onTap: () => _selectDate(_stopIssueDateController),
decoration: InputDecoration(
labelText: AppLocalizations.of(context).stopIssueDateLabel,
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.calendar_today),
onPressed: () => _selectDate(_stopIssueDateController),
),
),
keyboardType: TextInputType.datetime,
),
const SizedBox(height: 16),
TextFormField(
controller: _stopExpiryDateController,
readOnly: true,
onTap: () => _selectDate(_stopExpiryDateController),
decoration: InputDecoration(
labelText: AppLocalizations.of(context).stopExpiryDateLabel,
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.calendar_today),
onPressed: () => _selectDate(_stopExpiryDateController),
),
),
keyboardType: TextInputType.datetime,
),
@@ -206,39 +167,13 @@ class _StopSingleChequeScreenState extends State<StopSingleChequeScreen> {
keyboardType: TextInputType.number,
),
const SizedBox(height: 16),
DropdownButtonFormField<String>(
value: _selectedComment,
items: _commentOptions.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (newValue) {
setState(() {
_selectedComment = newValue;
_showOtherCommentField = newValue == 'Other';
});
},
TextFormField(
controller: _stopCommentController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).stopCommentHint,
border: const OutlineInputBorder(),
),
),
if (_showOtherCommentField)
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: TextFormField(
controller: _otherCommentController,
decoration: const InputDecoration(
labelText: "Other Reasons :",
border: OutlineInputBorder(),
),
validator: (value) {
return null;
},
),
),
const SizedBox(height: 16),
TextFormField(
initialValue: _formatDate(widget.date),
@@ -270,38 +205,23 @@ class _StopSingleChequeScreenState extends State<StopSingleChequeScreen> {
stopIssueDate: _stopIssueDateController.text,
stopExpiryDate: _stopExpiryDateController.text,
stopAmount: _stopAmountController.text,
stopComment: _selectedComment == 'Other'
? _otherCommentController.text
: _selectedComment ?? '',
stopComment: _stopCommentController.text,
chequeIssueDate: widget.date,
tpin: pin,
);
if (!mounted) return;
final decodedResponse = jsonDecode(response);
String responseString = response
.toString(); // used as the case only for incorrect TPIN
final status = decodedResponse['status'];
final message = decodedResponse['message'];
final code = decodedResponse['code'];
if (status == 'SUCCESS') {
_showResponseDialog('Success', message);
} else {
_showResponseDialog('Error', message);
}
if (status == 'ERROR') {
String errMessage = "error";
if (code == '0429') {
errMessage =
'The selected Cheque is already stopped';
} else if (code == '0748') {
errMessage =
'The selected Cheque is already presented';
}
_showResponseDialog('Error', errMessage);
}
if (responseString.contains('INCORRECT_TPIN')) {
_showResponseDialog('Invalid TPIN',
'The TPIN you entered is incorrect. Please try again.');
}
} on DioException catch (e) {
} on Exception catch (e) {
print('inside catch block');
print(e.toString());
try {
final errorBodyString =
e.toString().split('Exception: ')[1];

View File

@@ -11,11 +11,11 @@ import 'package:kmobile/features/auth/controllers/auth_state.dart';
import 'package:kmobile/features/cheque/screens/cheque_management_screen.dart';
import 'package:kmobile/features/customer_info/screens/customer_info_screen.dart';
import 'package:kmobile/features/beneficiaries/screens/manage_beneficiaries_screen.dart';
import 'package:kmobile/features/enquiry/screens/enquiry_screen.dart';
import 'package:kmobile/features/fund_transfer/screens/fund_transfer_screen.dart';
import 'package:kmobile/features/profile/profile_screen.dart';
import 'package:kmobile/features/quick_pay/screens/quick_pay_screen.dart';
import 'package:kmobile/features/account_opening/screens/account_opening_screen.dart';
import 'package:kmobile/features/yojna/screens/gov_scheme_screen.dart';
import 'package:kmobile/features/service/screens/branch_locator_screen.dart';
import 'package:kmobile/security/secure_storage.dart';
import 'package:local_auth/local_auth.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
@@ -618,15 +618,14 @@ class _DashboardScreenState extends State<DashboardScreen>
selectedIndex: selectedAccountIndex,
)));
}),
_buildQuickLink(Symbols.box, AppLocalizations.of(context).createnewdeposit, () {
_buildQuickLink(Icons.location_pin,
AppLocalizations.of(context).branchlocator, () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AccountOpeningScreen(
users: users,
selectedIndex: selectedAccountIndex,
)));
}, disable: isPaymentDisabled),
builder: (context) =>
const BranchLocatorScreen()));
}, disable: false),
_buildQuickLink(Icons.group,
AppLocalizations.of(context).manageBeneficiary,
() {
@@ -637,20 +636,14 @@ class _DashboardScreenState extends State<DashboardScreen>
ManageBeneficiariesScreen(
customerName: currAccount.name!)));
}, disable: false),
_buildQuickLink(
Symbols.family_group,
AppLocalizations.of(context).governmentSchemes,
() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => GovSchemeScreen(
users: users,
selectedIndex: selectedAccountIndex,
)));
},
disable: isPaymentDisabled,
),
_buildQuickLink(Symbols.support_agent,
AppLocalizations.of(context).contactUs, () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const EnquiryScreen()));
}),
_buildQuickLink(
Symbols.checkbook,
AppLocalizations.of(context).chequeManagement,

View File

@@ -74,7 +74,7 @@ class SecuritySettingsScreen extends StatelessWidget {
margin: const EdgeInsets.only(bottom: 10),
child: ListTile(
leading: const Icon(Icons.password),
title: Text(AppLocalizations.of(context).changeTpin),
title: const Text('Change TPIN'),
trailing: const Icon(Icons.chevron_right),
onTap: () async {
final authService = getIt<AuthService>();

View File

@@ -1,8 +1,4 @@
import 'package:kmobile/features/account_opening/screens/account_opening_screen.dart';
import 'package:kmobile/features/service/screens/atm_locator_screen.dart';
import 'package:kmobile/features/service/screens/branch_locator_screen.dart';
import 'package:kmobile/features/service/screens/enquiry_screen.dart';
import 'package:kmobile/features/service/screens/upi_screen.dart';
import '../../../l10n/app_localizations.dart';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
@@ -47,6 +43,7 @@ class _ServiceScreen extends State<ServiceScreen> {
disabled: false,
),
),
const SizedBox(height: 16),
Expanded(
child: ServiceManagementTile(
icon: Symbols.question_mark,
@@ -60,23 +57,10 @@ class _ServiceScreen extends State<ServiceScreen> {
disabled: false,
),
),
const SizedBox(height: 16),
Expanded(
child: ServiceManagementTile(
icon: Icons.location_pin,
label: AppLocalizations.of(context).branchlocator,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const BranchLocatorScreen()));
},
disabled: false,
),
),
Expanded(
child: ServiceManagementTile(
icon: Symbols.payment,
icon: Symbols.location_pin,
label: AppLocalizations.of(context).atmlocator,
onTap: () {
Navigator.push(
@@ -87,33 +71,7 @@ class _ServiceScreen extends State<ServiceScreen> {
disabled: false,
),
),
// Expanded(
// child: ServiceManagementTile(
// icon: Symbols.upi_pay,
// label: "Receive Money by UPI",
// onTap: () {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => const UpiScreen()));
// },
// disabled: false,
// ),
// ),
Expanded(
child: ServiceManagementTile(
icon: Symbols.support_agent,
label: AppLocalizations.of(context).contactUs,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const EnquiryScreen()));
},
disabled: false,
),
),
// No Spacer() needed here as Expanded children will fill space
],
),
),
@@ -164,31 +122,29 @@ class ServiceManagementTile extends StatelessWidget {
onTap:
disabled ? null : onTap, // Disable InkWell if the tile is disabled
borderRadius: BorderRadius.circular(12.0),
child: Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
size: 48, // Make icon larger
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
size: 48, // Make icon larger
color:
disabled ? theme.disabledColor : theme.colorScheme.primary,
),
const SizedBox(height: 12),
Text(
label,
textAlign: TextAlign.center,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: disabled
? theme.disabledColor
: theme.colorScheme.primary,
: theme.colorScheme.onSurface,
),
const SizedBox(height: 12),
Text(
label,
textAlign: TextAlign.center,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: disabled
? theme.disabledColor
: theme.colorScheme.onSurface,
),
),
],
),
),
],
),
),
),

View File

@@ -1,137 +0,0 @@
import 'package:flutter/material.dart';
import 'package:qr_flutter/qr_flutter.dart';
class UpiScreen extends StatefulWidget {
const UpiScreen({super.key});
@override
State<UpiScreen> createState() => _UpiScreenState();
}
class _UpiScreenState extends State<UpiScreen> {
final TextEditingController accountCtrl = TextEditingController();
final TextEditingController ifscCtrl = TextEditingController();
final TextEditingController nameCtrl = TextEditingController();
String? upiUri;
/// Build UPI URI using Account Number + IFSC
/// Follows NPCI UPI URI standards (upi://pay?pa=...&pn=...&cu=INR)
/// Supported by UPI QR generators like Labnol which accept bank account + IFSC as payment address.
String buildUpiUri({
required String accountNumber,
required String ifsc,
required String name,
}) {
final upiAddress = "$accountNumber@apl";
//const upiAddress = "asifarbaj-2@okaxis";
final uri = Uri(
scheme: "upi",
host: "pay",
queryParameters: {
"pa": upiAddress,
"pn": name,
"cu": "INR",
},
);
return uri.toString();
}
void generateQr() {
final account = accountCtrl.text.trim();
final ifsc = ifscCtrl.text.trim();
final name = nameCtrl.text.trim();
if (account.isEmpty || ifsc.isEmpty || name.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Please fill all fields")),
);
return;
}
setState(() {
upiUri = buildUpiUri(
accountNumber: account,
ifsc: ifsc,
name: name,
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Receive Money by UPI"),
centerTitle: true,
),
body: Padding(
padding: const EdgeInsets.all(20),
child: SingleChildScrollView(
child: Column(
children: [
TextField(
controller: accountCtrl,
decoration: const InputDecoration(
labelText: "Account Number",
border: OutlineInputBorder(),
),
keyboardType: TextInputType.number,
),
const SizedBox(height: 15),
TextField(
controller: ifscCtrl,
decoration: const InputDecoration(
labelText: "IFSC Code",
border: OutlineInputBorder(),
),
),
const SizedBox(height: 15),
TextField(
controller: nameCtrl,
decoration: const InputDecoration(
labelText: "Account Holder Name",
border: OutlineInputBorder(),
),
),
const SizedBox(height: 25),
ElevatedButton(
onPressed: generateQr,
child: const Text("Generate QR"),
),
const SizedBox(height: 30),
if (upiUri != null) ...[
const Text(
"Your UPI QR Code:",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 15),
QrImageView(
data: upiUri!,
version: QrVersions.auto,
size: 260,
backgroundColor: Colors.white,
),
const SizedBox(height: 10),
SelectableText(
upiUri!,
textAlign: TextAlign.center,
),
],
],
),
),
),
);
}
}

View File

@@ -1,660 +0,0 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:kmobile/api/services/yojna_service.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class APYRegisterScreen extends StatefulWidget {
final Map<String, dynamic>? initialData;
const APYRegisterScreen({super.key, this.initialData});
@override
State<APYRegisterScreen> createState() => _APYRegisterScreenState();
}
class _APYRegisterScreenState extends State<APYRegisterScreen> {
final _formKey = GlobalKey<FormState>();
// Helper to format initial date string (DDMMYYYY -> DD/MM/YYYY)
String _formatInitialDate(dynamic date) {
if (date == null) return '';
String dateStr = date.toString();
if (dateStr.length == 8) {
return "${dateStr.substring(0, 2)}/${dateStr.substring(2, 4)}/${dateStr.substring(4)}";
}
return dateStr;
}
// Controllers initialized strictly from initialData where available, otherwise empty.
late final _titleController = TextEditingController(
text: widget.initialData?['customertitle']?.toString());
late final _firstNameController = TextEditingController(
text: widget.initialData?['customerfirstname']?.toString());
late final _middleNameController = TextEditingController(
text: widget.initialData?['customermiddlename']?.toString());
late final _lastNameController = TextEditingController(
text: widget.initialData?['customerlastname']?.toString());
late final _customerNoController = TextEditingController(
text: widget.initialData?['customerno']?.toString());
late final _accountNoController = TextEditingController(
text: widget.initialData?['accountno']?.toString());
late final _balanceController = TextEditingController(
text: widget.initialData?['availablebalance']?.toString());
late final _dobController = TextEditingController(
text: _formatInitialDate(widget.initialData?['customerdob']));
late final _genderController = TextEditingController(
text: widget.initialData?['gender']?.toString());
late final _aadhaarController = TextEditingController(
text: widget.initialData?['aadharno']?.toString());
late final _ageOfJoiningController = TextEditingController(
text: widget.initialData?['ageofjoining']?.toString());
late final _emailController = TextEditingController(
text: widget.initialData?['emailid']?.toString());
late final _modeOfCollectionController = TextEditingController(
text: widget.initialData?['modeofcollection']?.toString());
// Fields that must be filled by the user (no defaults)
late final _marriedController = TextEditingController();
late final _mobileController = TextEditingController();
late final _pincodeController = TextEditingController();
late final _pensionAmountController = TextEditingController();
late final _spouseNameController = TextEditingController();
late final _incomeTaxPayerController = TextEditingController();
late final _otherSchemeController = TextEditingController();
late final _collectionChannelController = TextEditingController();
late final _contributionTypeController = TextEditingController();
late final _debitDateController = TextEditingController();
late final _nomineeNameController = TextEditingController();
late final _nomineeRelationController = TextEditingController();
late final _nomineeMinorController = TextEditingController();
late final _nomineeDobController = TextEditingController();
late final _guardianNameController = TextEditingController();
late final _fatcaController = TextEditingController();
late final _birthCountryController = TextEditingController();
late final _citizenshipCountryController = TextEditingController();
late final _taxResidenceCountryController = TextEditingController();
late final _usPersonController = TextEditingController();
late final _secondNomineeNameController = TextEditingController();
late final _secondNomineeMinorController = TextEditingController();
late final _secondNomineeRelationController = TextEditingController();
late final _fatcaCountController = TextEditingController();
late final _docCitizenshipFlagController = TextEditingController();
late final _reasonNoEvidenceController = TextEditingController();
late final _docNameEvidenceController = TextEditingController();
final Map<String, String> _yesNoOptions = {
'Y': 'Yes',
'N': 'No',
};
final Map<String, String> _titleOptions = {
'01': 'Mr.',
'02': 'Mrs.',
'03': 'Miss',
};
final Map<String, String> _genderOptions = {
'M': 'Male',
'F': 'Female',
'O': 'Other',
};
final Map<String, String> _pensionOptions = {
'1000': '₹1000',
'2000': '₹2000',
'3000': '₹3000',
'4000': '₹4000',
'5000': '₹5000',
};
final Map<String, String> _collectionChannelOptions = {
'1': 'Bank',
};
Map<String, String> _getPeriodicityOptions(AppLocalizations l10n) => {
'C': l10n.monthly,
'Q': l10n.quarterly,
'H': l10n.halfYearly,
};
// Contribution Calculation Map
final Map<String, Map<String, String>> _contributionRates = {
'C': {
'1000': '90',
'2000': '178',
'3000': '268',
'4000': '356',
'5000': '446',
},
'Q': {
'1000': '268',
'2000': '530',
'3000': '1061',
'4000': '356', // Following prompt's example exactly
'5000': '1329',
},
'H': {
'1000': '531',
'2000': '1050',
'3000': '1582',
'4000': '2101',
'5000': '2632',
},
};
String get _calculatedContribution {
final periodicity = _contributionTypeController.text;
final amount = _pensionAmountController.text;
return _contributionRates[periodicity]?[amount] ?? '0';
}
@override
void dispose() {
_titleController.dispose();
_firstNameController.dispose();
_middleNameController.dispose();
_lastNameController.dispose();
_customerNoController.dispose();
_accountNoController.dispose();
_balanceController.dispose();
_dobController.dispose();
_genderController.dispose();
_marriedController.dispose();
_mobileController.dispose();
_emailController.dispose();
_aadhaarController.dispose();
_pincodeController.dispose();
_pensionAmountController.dispose();
_ageOfJoiningController.dispose();
_spouseNameController.dispose();
_incomeTaxPayerController.dispose();
_otherSchemeController.dispose();
_collectionChannelController.dispose();
_contributionTypeController.dispose();
_debitDateController.dispose();
_nomineeNameController.dispose();
_nomineeRelationController.dispose();
_nomineeMinorController.dispose();
_nomineeDobController.dispose();
_guardianNameController.dispose();
_fatcaController.dispose();
_birthCountryController.dispose();
_citizenshipCountryController.dispose();
_taxResidenceCountryController.dispose();
_usPersonController.dispose();
_secondNomineeNameController.dispose();
_secondNomineeMinorController.dispose();
_secondNomineeRelationController.dispose();
_fatcaCountController.dispose();
_docCitizenshipFlagController.dispose();
_reasonNoEvidenceController.dispose();
_docNameEvidenceController.dispose();
_modeOfCollectionController.dispose();
super.dispose();
}
Future<void> _selectDate(BuildContext context, TextEditingController controller) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime(2100),
);
if (picked != null) {
setState(() {
// Display format: DD/MM/YYYY
controller.text = DateFormat('dd/MM/yyyy').format(picked);
});
}
}
void _handleRegister() async {
if (_formKey.currentState!.validate()) {
final age = int.tryParse(_ageOfJoiningController.text) ?? 0;
if (age < 18 || age > 40) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: const Text('Age Restriction'),
content: const Text(
'Age of joining must be between 18 and 40 years to register for APY.'),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context); // Close dialog
Navigator.popUntil(context, (route) => route.isFirst);
},
child: const Text('OK'),
),
],
),
);
return;
}
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const Center(child: CircularProgressIndicator()),
);
try {
final yojnaService = getIt<YojnaService>();
final response = await yojnaService.registerAPY(
accountno: _accountNoController.text,
customerfirstname: _firstNameController.text,
customermiddlename: _middleNameController.text,
customerlastname: _lastNameController.text,
availablebalance: _balanceController.text,
// Remove slashes for API (DD/MM/YYYY -> DDMMYYYY)
customerdob: _dobController.text.replaceAll('/', ''),
emailid: _emailController.text,
gender: _genderController.text,
married: _marriedController.text,
nomineename: _nomineeNameController.text,
relationwithsubscriber: _nomineeRelationController.text,
mobilenumber: _mobileController.text,
nomineeminor: _nomineeMinorController.text,
customerno: _customerNoController.text,
beneficaryofothersociatysecurityschemes: _otherSchemeController.text,
whetherincometaxpayer: _incomeTaxPayerController.text,
customertitle: _titleController.text,
aadharno: _aadhaarController.text,
nameofspouse: _spouseNameController.text,
ageofjoining: _ageOfJoiningController.text,
pensionamtoptedfor: _pensionAmountController.text,
montlycontributioncalculate: _calculatedContribution,
collectionchannel: _collectionChannelController.text,
// Remove slashes for API
subsequentContributionDebitDate: _debitDateController.text.replaceAll('/', ''),
secondnomineeminor: _secondNomineeMinorController.text,
secondnomineename: _secondNomineeNameController.text,
secondrelationshipwithsubscriber: _secondNomineeRelationController.text,
pincode: _pincodeController.text,
fatcacrsapplicable: _fatcaController.text,
countryofbirth: _birthCountryController.text,
countryofcitizenship: _citizenshipCountryController.text,
countryofresidencefortaxpurpose: _taxResidenceCountryController.text,
uspersonflag: _usPersonController.text,
fatcadeclarationcount: _fatcaCountController.text,
documentevidencingcitizenshipflag: _docCitizenshipFlagController.text,
reasonfornoevidence: _reasonNoEvidenceController.text,
nameofdocumentforcitizenshipevidence: _docNameEvidenceController.text,
modeofcollection: _modeOfCollectionController.text,
contributionType: _contributionTypeController.text,
);
if (!mounted) return;
Navigator.pop(context); // Close loading dialog
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Registration Result'),
content: SingleChildScrollView(
child: Text(response.toString()),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
} catch (e) {
if (!mounted) return;
Navigator.pop(context); // Close loading dialog
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Error'),
content: Text('Failed to register: $e'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
}
}
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(l10n.apyRegistration),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Tile 1: Customer Details
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.personaldetails,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildDropdownField(
_titleController, l10n.customerTitle, _titleOptions),
_buildTextField(
_firstNameController, l10n.customerFirstName),
_buildTextField(
_middleNameController, l10n.customerMiddleName),
_buildTextField(
_lastNameController, l10n.customerLastName),
_buildTextField(_customerNoController, l10n.customerNo),
_buildTextField(_accountNoController, l10n.accountNumber,
keyboardType: TextInputType.number),
_buildTextField(_balanceController, l10n.availableBalance,
keyboardType: TextInputType.number),
_buildTextField(_dobController, l10n.customerDobFormat,
mandatory: true, onTap: () => _selectDate(context, _dobController)),
_buildDropdownField(
_genderController, l10n.gender, _genderOptions),
_buildTextField(_mobileController, l10n.mobileNumber,
keyboardType: TextInputType.phone),
_buildTextField(_emailController, l10n.emailId,
keyboardType: TextInputType.emailAddress),
_buildTextField(_aadhaarController, l10n.aadhaarNo,
keyboardType: TextInputType.number),
_buildTextField(_pincodeController, l10n.pincode,
keyboardType: TextInputType.number),
_buildDropdownField(
_marriedController, l10n.marriedYesNo, _yesNoOptions,
mandatory: true),
],
),
),
),
const SizedBox(height: 16),
// Tile 2: Nominee Details
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.nomineeDetails,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
Text("Nominee 1", style: TextStyle(fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.primary)),
const SizedBox(height: 8),
_buildTextField(_nomineeNameController, l10n.nomineeName,
mandatory: true),
_buildTextField(_nomineeRelationController, l10n.relationWithSubscriber,
mandatory: true),
_buildDropdownField(_nomineeMinorController,
l10n.nomineeMinor, _yesNoOptions, mandatory: true,
onChanged: (val) {
setState(() {
_nomineeMinorController.text = val ?? '';
});
}),
if (_nomineeMinorController.text == 'Y') ...[
_buildTextField(_nomineeDobController, l10n.nomineeDob,
mandatory: true, onTap: () => _selectDate(context, _nomineeDobController)),
_buildTextField(
_guardianNameController, l10n.guardianName,
mandatory: true),
],
const Divider(height: 32),
Text("Nominee 2", style: TextStyle(fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.primary)),
const SizedBox(height: 8),
_buildTextField(_secondNomineeNameController, l10n.secondNomineeName),
_buildTextField(_secondNomineeRelationController, l10n.secondNomineeRelationship),
_buildDropdownField(_secondNomineeMinorController,
l10n.secondNomineeMinor, _yesNoOptions,
onChanged: (val) {
setState(() {
_secondNomineeMinorController.text = val ?? '';
});
}),
],
),
),
),
const SizedBox(height: 16),
// Tile 3: Scheme & APY Details
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.schemeDetails,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildDropdownField(_otherSchemeController,
l10n.isBeneficiaryOtherScheme, _yesNoOptions,
mandatory: true),
_buildDropdownField(_incomeTaxPayerController,
l10n.isIncomeTaxPayer, _yesNoOptions,
mandatory: true),
_buildTextField(_spouseNameController, l10n.nameOfSpouse,
mandatory: true),
_buildTextField(_ageOfJoiningController, l10n.ageOfJoining,
keyboardType: TextInputType.number, mandatory: true),
_buildDropdownField(_collectionChannelController,
l10n.collectionChannel, _collectionChannelOptions,
mandatory: true),
_buildTextField(_modeOfCollectionController, l10n.modeOfCollection),
],
),
),
),
const SizedBox(height: 16),
// Tile 4: FATCA / CRS Details (KYC Details)
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.kycdetails,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildDropdownField(_fatcaController,
l10n.fatcaApplicable, _yesNoOptions),
_buildTextField(
_birthCountryController, l10n.countryOfBirth),
_buildTextField(_citizenshipCountryController,
l10n.countryOfCitizenship),
_buildTextField(_taxResidenceCountryController,
l10n.countryOfTaxResidence),
_buildDropdownField(_usPersonController,
l10n.usPersonFlag, _yesNoOptions),
_buildTextField(_fatcaCountController, "FATCA Declaration Count"),
_buildTextField(_docCitizenshipFlagController, "Doc Evidencing Citizenship Flag"),
_buildTextField(_reasonNoEvidenceController, "Reason for No Evidence"),
_buildTextField(_docNameEvidenceController, "Doc Name for Citizenship Evidence"),
],
),
),
),
const SizedBox(height: 16),
// Tile 5: Pension & Contribution Settings
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.contributionAmount,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildDropdownField(_pensionAmountController,
l10n.pensionAmount, _pensionOptions, mandatory: true,
onChanged: (val) {
setState(() {
_pensionAmountController.text = val ?? '';
});
}),
_buildDropdownField(_contributionTypeController,
l10n.periodicity, _getPeriodicityOptions(l10n),
mandatory: true, onChanged: (val) {
setState(() {
_contributionTypeController.text = val ?? '';
});
}),
_buildTextField(
_debitDateController, l10n.subsequentDebitDate,
mandatory: true, onTap: () => _selectDate(context, _debitDateController)),
],
),
),
),
const SizedBox(height: 16),
// Tile 6: Contribution Amount Summary
Card(
elevation: 2,
color: Theme.of(context).colorScheme.secondaryContainer,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Text(
'${l10n.contributionAmount} (${_getPeriodicityOptions(l10n)[_contributionTypeController.text]})',
style: TextStyle(
fontSize: 16,
color: Theme.of(context)
.colorScheme
.onSecondaryContainer)),
const SizedBox(height: 8),
Text('$_calculatedContribution',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.colorScheme
.onSecondaryContainer)),
],
),
),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _handleRegister,
style: ElevatedButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.primaryContainer,
foregroundColor:
Theme.of(context).colorScheme.onPrimaryContainer,
padding: const EdgeInsets.symmetric(vertical: 16),
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
l10n.register,
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 32),
],
),
),
),
);
}
Widget _buildDropdownField(TextEditingController controller, String label,
Map<String, String> options,
{bool readOnly = false,
bool mandatory = false,
void Function(String?)? onChanged}) {
String? currentValue =
options.containsKey(controller.text) ? controller.text : null;
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: DropdownButtonFormField<String>(
value: currentValue,
onChanged: readOnly
? null
: (newValue) {
if (newValue != null) {
setState(() {
controller.text = newValue;
});
}
if (onChanged != null) {
onChanged(newValue);
}
},
decoration: InputDecoration(
labelText: mandatory ? '$label *' : label,
border: const OutlineInputBorder(),
contentPadding:
const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
),
validator: (value) {
if (mandatory && (value == null || value.isEmpty)) {
return 'This field is required';
}
return null;
},
items: options.entries.map((entry) {
return DropdownMenuItem<String>(
value: entry.key,
child: Text(entry.value),
);
}).toList(),
),
);
}
Widget _buildTextField(TextEditingController controller, String label,
{TextInputType keyboardType = TextInputType.text,
bool readOnly = false,
bool mandatory = false,
VoidCallback? onTap}) {
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: TextFormField(
controller: controller,
readOnly: readOnly || onTap != null,
onTap: onTap,
decoration: InputDecoration(
labelText: mandatory ? '$label *' : label,
border: const OutlineInputBorder(),
suffixIcon: onTap != null ? const Icon(Icons.calendar_today) : null,
contentPadding:
const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
),
keyboardType: keyboardType,
validator: (value) {
if (mandatory && (value == null || value.isEmpty)) {
return 'This field is required';
}
return null;
},
),
);
}
}

View File

@@ -1,194 +0,0 @@
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/yojna_service.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/features/yojna/screens/apy_register_screen.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class APYScreen extends StatefulWidget {
final List<User> users;
final int selectedIndex;
const APYScreen({
super.key,
required this.users,
required this.selectedIndex,
});
@override
State<APYScreen> createState() => _APYScreenState();
}
class _APYScreenState extends State<APYScreen> {
User? _selectedAccount;
List<User> _filteredUsers = [];
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
@override
void initState() {
super.initState();
_filteredUsers = widget.users
.where((user) => ['SA', 'SB', 'CA', 'CC'].contains(user.accountType))
.toList();
// Pre-fill the account number if possible
if (widget.users.isNotEmpty && widget.selectedIndex < widget.users.length) {
if (_filteredUsers.isNotEmpty) {
if (_filteredUsers.contains(widget.users[widget.selectedIndex])) {
_selectedAccount = widget.users[widget.selectedIndex];
} else {
_selectedAccount = _filteredUsers.first;
}
} else {
_selectedAccount = widget.users[widget.selectedIndex];
}
} else {
if (_filteredUsers.isNotEmpty) {
_selectedAccount = _filteredUsers.first;
}
}
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(l10n.apyRegistration),
centerTitle: false,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
l10n.apyDescription,
style: Theme.of(context).textTheme.titleMedium,
),
),
),
const SizedBox(height: 16),
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DropdownButtonFormField<User>(
value: _selectedAccount,
decoration: InputDecoration(
labelText: l10n.accountNumber,
border: const OutlineInputBorder(),
contentPadding: const EdgeInsets.symmetric(
vertical: 20, horizontal: 12),
),
items: _filteredUsers.map((user) {
return DropdownMenuItem<User>(
value: user,
child: Text(user.accountNo.toString()),
);
}).toList(),
onChanged: (User? newUser) {
setState(() {
_selectedAccount = newUser;
});
},
validator: (value) {
if (value == null) {
return l10n.accountNumberRequired;
}
return null;
},
),
],
),
),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _isLoading
? null
: () async {
if (_formKey.currentState!.validate()) {
setState(() {
_isLoading = true;
});
try {
final response = await getIt<YojnaService>()
.fetchpapydetails(
accountno: _selectedAccount!.accountNo ?? '');
if (mounted) {
if (response != null) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => APYRegisterScreen(
initialData: response),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
"Failed to fetch details. Please try again.")),
);
}
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Error: ${e.toString()}")),
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
},
style: ElevatedButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.primaryContainer,
foregroundColor:
Theme.of(context).colorScheme.onPrimaryContainer,
minimumSize: const Size(double.infinity, 50),
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: _isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: Text(
l10n.proceedButton,
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.bold),
),
),
],
),
),
),
);
}
}

View File

@@ -1,165 +0,0 @@
import 'package:flutter/material.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/features/yojna/screens/apy_screen.dart';
import 'package:kmobile/features/yojna/screens/pm_main_screen.dart';
import '../../../l10n/app_localizations.dart';
class GovSchemeScreen extends StatefulWidget {
final List<User> users;
final int selectedIndex;
const GovSchemeScreen({
super.key,
required this.users,
required this.selectedIndex,
});
@override
State<GovSchemeScreen> createState() => _GovSchemeScreenState();
}
class _GovSchemeScreenState extends State<GovSchemeScreen> {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(l10n.governmentSchemes),
),
body: Stack(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: GovSchemeTile(
logoText: "PMJJBY/PMSBY",
label: l10n.pradhanMantriYojana,
subtitle: l10n.enrollPMJJBYPMSBY,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PMMainScreen(
users: widget.users,
selectedIndex: widget.selectedIndex,
),
),
);
},
),
),
Expanded(
child: GovSchemeTile(
logoText: "APY",
label: l10n.registerForAtalPensionYojana,
subtitle: l10n.secureYourFutureAPY,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => APYScreen(
users: widget.users,
selectedIndex: widget.selectedIndex,
),
),
); // Action for APY will be added later
},
),
),
],
),
),
IgnorePointer(
child: Center(
child: Opacity(
opacity: 0.07,
child: ClipOval(
child: Image.asset(
'assets/images/logo.png',
width: 200,
height: 200,
),
),
),
),
),
],
),
);
}
}
class GovSchemeTile extends StatelessWidget {
final String logoText;
final String label;
final String? subtitle;
final VoidCallback onTap;
final bool disable;
const GovSchemeTile({
super.key,
required this.logoText,
required this.label,
this.subtitle,
required this.onTap,
this.disable = false,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
elevation: 4,
child: InkWell(
onTap: disable ? null : onTap,
borderRadius: BorderRadius.circular(12.0),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 36.0, horizontal: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
logoText,
style: TextStyle(
fontSize: logoText.length > 5 ? 28 : 40,
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
),
),
const SizedBox(height: 16),
Text(
label,
textAlign: TextAlign.center,
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
color: disable
? theme.disabledColor
: theme.colorScheme.onSurface,
),
),
if (subtitle != null)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
subtitle!,
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium?.copyWith(
color: disable
? theme.disabledColor
: theme.colorScheme.onSurfaceVariant,
),
),
),
],
),
),
),
);
}
}

View File

@@ -1,340 +0,0 @@
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/yojna_service.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/features/yojna/screens/pmjjby_screen.dart';
import 'package:kmobile/features/yojna/screens/pmsby_screen.dart';
import 'package:kmobile/features/yojna/screens/pmjjby_enquiry_screen.dart';
import 'package:kmobile/features/yojna/screens/pmsby_enquiry_screen.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class PMMainScreen extends StatefulWidget {
final List<User> users;
final int selectedIndex;
const PMMainScreen({
super.key,
required this.users,
required this.selectedIndex,
});
@override
State<PMMainScreen> createState() => _PMMainScreenState();
}
class _PMMainScreenState extends State<PMMainScreen> {
User? _selectedAccount;
List<User> _filteredUsers = [];
String? _selectedScheme;
List<String> _getSchemes(AppLocalizations l10n) => [
l10n.pmjjbyFull,
l10n.pmsbyFull,
];
@override
void initState() {
super.initState();
_filteredUsers = widget.users
.where((user) => ['SA', 'SB', 'CA', 'CC'].contains(user.accountType))
.toList();
// Pre-fill the account number if possible
if (widget.users.isNotEmpty && widget.selectedIndex < widget.users.length) {
if (_filteredUsers.isNotEmpty) {
if (_filteredUsers.contains(widget.users[widget.selectedIndex])) {
_selectedAccount = widget.users[widget.selectedIndex];
} else {
_selectedAccount = _filteredUsers.first;
}
} else {
_selectedAccount = widget.users[widget.selectedIndex];
}
} else {
if (_filteredUsers.isNotEmpty) {
_selectedAccount = _filteredUsers.first;
}
}
}
@override
void dispose() {
super.dispose();
}
Future<void> _handleCreate() async {
final l10n = AppLocalizations.of(context);
if (_selectedAccount == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.pleaseSelectAccountNumber)),
);
return;
}
if (_selectedScheme == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.pleaseSelectSchemeFirst)),
);
return;
}
final String schemeCode =
(_selectedScheme == l10n.pmjjbyFull) ? '02' : '01';
// Show loading
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const CircularProgressIndicator(),
const SizedBox(width: 16),
Text(l10n.fetchingDetails),
],
),
duration: const Duration(seconds: 2),
),
);
try {
final response = await getIt<YojnaService>().fetchpmydetails(
scheme: schemeCode,
action: 'C',
accountno: _selectedAccount!.accountNo!,
);
if (mounted) {
Map<String, dynamic>? data;
if (response is Map<String, dynamic>) {
data = response;
} else if (response is List &&
response.isNotEmpty &&
response[0] is Map<String, dynamic>) {
data = response[0] as Map<String, dynamic>;
}
if (data != null && data.isNotEmpty) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
if (_selectedScheme == l10n.pmjjbyFull) {
return PMJJBYScreen(initialData: data!);
} else {
return PMSBYScreen(initialData: data!);
}
},
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.failedToFetchDetails)),
);
}
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.genericError(e.toString()))),
);
}
}
}
void _handleEnquiry() {
final l10n = AppLocalizations.of(context);
if (_selectedAccount == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.pleaseSelectAccountNumber)),
);
return;
}
if (_selectedScheme == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.pleaseSelectSchemeFirst)),
);
return;
}
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
if (_selectedScheme == l10n.pmjjbyFull) {
return PMJJBYEnquiryScreen(cifNumber: _selectedAccount!.cifNumber);
} else {
return PMSBYEnquiryScreen(cifNumber: _selectedAccount!.cifNumber);
}
},
),
);
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final schemes = _getSchemes(l10n);
// Ensure _selectedScheme is valid if it was set in a different language
if (_selectedScheme != null && !schemes.contains(_selectedScheme)) {
// Try to find the corresponding scheme in the new language
// This is a bit tricky, but since we only have two:
// If it doesn't match, it might be from the other language.
// For simplicity, we can just reset it or try to guess.
// Better to use non-localized values for the state, but let's just reset for now if it doesn't match
_selectedScheme = null;
}
return Scaffold(
appBar: AppBar(
title: Text(l10n.pradhanMantriYojana),
centerTitle: false,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
l10n.pmjjbyPmsbyDescription,
style: Theme.of(context).textTheme.titleMedium,
),
),
),
const SizedBox(height: 16),
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DropdownButtonFormField<User>(
value: _selectedAccount,
decoration: InputDecoration(
labelText: l10n.accountNumber,
border: const OutlineInputBorder(),
contentPadding: const EdgeInsets.symmetric(
vertical: 20, horizontal: 12),
),
items: _filteredUsers.map((user) {
return DropdownMenuItem<User>(
value: user,
child: Text(user.accountNo.toString()),
);
}).toList(),
onChanged: (User? newUser) {
setState(() {
_selectedAccount = newUser;
});
},
validator: (value) {
if (value == null) {
return l10n.accountNumberRequired;
}
return null;
},
),
const SizedBox(height: 16),
DropdownButtonFormField<String>(
value: _selectedScheme,
isExpanded: true,
isDense: false,
itemHeight: null,
decoration: InputDecoration(
labelText: l10n.selectScheme,
border: const OutlineInputBorder(),
contentPadding: const EdgeInsets.symmetric(
vertical: 12, horizontal: 12),
),
selectedItemBuilder: (BuildContext context) {
return schemes.map((String scheme) {
return Container(
alignment: Alignment.centerLeft,
child: Text(
scheme,
style: const TextStyle(fontSize: 14),
softWrap: true,
maxLines: 2,
overflow: TextOverflow.visible,
),
);
}).toList();
},
items: schemes.map((String scheme) {
return DropdownMenuItem<String>(
value: scheme,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: Text(
scheme,
style: const TextStyle(fontSize: 15),
softWrap: true,
),
),
);
}).toList(),
onChanged: (String? newValue) {
setState(() {
_selectedScheme = newValue;
});
},
),
],
),
),
),
const SizedBox(height: 24),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: _handleCreate,
style: ElevatedButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.primaryContainer,
foregroundColor:
Theme.of(context).colorScheme.onPrimaryContainer,
padding: const EdgeInsets.symmetric(vertical: 16),
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
l10n.create,
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
),
const SizedBox(width: 16),
Expanded(
child: ElevatedButton(
onPressed: _handleEnquiry,
style: ElevatedButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.primaryContainer,
foregroundColor:
Theme.of(context).colorScheme.onPrimaryContainer,
padding: const EdgeInsets.symmetric(vertical: 16),
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
l10n.enquiry,
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
),
],
),
],
),
),
);
}
}

View File

@@ -1,229 +0,0 @@
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/yojna_service.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class PMJJBYEnquiryScreen extends StatefulWidget {
final String? cifNumber;
const PMJJBYEnquiryScreen({
super.key,
required this.cifNumber,
});
@override
State<PMJJBYEnquiryScreen> createState() => _PMJJBYEnquiryScreenState();
}
class _PMJJBYEnquiryScreenState extends State<PMJJBYEnquiryScreen> {
String? _selectedFinancialYear;
bool _isLoading = false;
Map<String, dynamic>? _enquiryData;
String? _errorMessage;
final List<String> _financialYears = [
'2021-2022',
'2022-2023',
'2023-2024',
'2024-2025',
'2025-2026',
];
@override
void initState() {
super.initState();
_selectedFinancialYear = null;
}
Future<void> _fetchEnquiryData() async {
final l10n = AppLocalizations.of(context);
if (_selectedFinancialYear == null || widget.cifNumber == null) return;
setState(() {
_isLoading = true;
_enquiryData = null;
_errorMessage = null;
});
final formattedYear = _selectedFinancialYear!.replaceAll('-', '');
try {
final response = await getIt<YojnaService>().enquiry(
scheme: '02',
action: 'E',
financialyear: formattedYear,
customerno: widget.cifNumber,
);
setState(() {
if (response is Map<String, dynamic>) {
if (response['status'] == 'FAILED') {
_errorMessage = response['message'] ?? l10n.noRecordFound;
} else {
_enquiryData = response;
}
} else if (response is List && response.isNotEmpty) {
_enquiryData = response[0] as Map<String, dynamic>;
} else {
_errorMessage = l10n.noDataFoundYear;
}
});
} catch (e) {
setState(() {
_errorMessage = l10n.genericError(e.toString());
});
} finally {
setState(() {
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(l10n.pmjjbyDetails),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.person, color: Colors.blue),
title: Text(l10n.cifNumber),
subtitle: Text(
widget.cifNumber ?? l10n.notApplicable,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
),
const SizedBox(height: 16),
DropdownButtonFormField<String>(
value: _selectedFinancialYear,
decoration: InputDecoration(
labelText: l10n.selectFinancialYear,
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.calendar_today),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 8),
),
items: _financialYears.map((String year) {
return DropdownMenuItem<String>(
value: year,
child: Text(year),
);
}).toList(),
onChanged: (String? newValue) {
setState(() {
_selectedFinancialYear = newValue;
});
_fetchEnquiryData();
},
),
],
),
),
),
const SizedBox(height: 20),
if (_isLoading)
const Center(
child: Padding(
padding: EdgeInsets.all(20.0),
child: CircularProgressIndicator(),
),
)
else if (_errorMessage != null)
Card(
color: Colors.red.shade50,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
_errorMessage!,
style: TextStyle(
color: Colors.red.shade700,
fontWeight: FontWeight.bold),
//textAlign: Center,
),
),
)
else if (_enquiryData != null)
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.schemeDetails,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
),
const Divider(),
_buildDetailRow(
l10n.customerName, _enquiryData!['customername']),
_buildDetailRow(
l10n.policyNumber, _enquiryData!['policynumber']),
_buildDetailRow(
l10n.accountNumber, _enquiryData!['accountno']),
_buildDetailRow(
l10n.premiumAmount, _enquiryData!['preimiumamount']),
_buildDetailRow(
l10n.nomineeName, _enquiryData!['nomineename']),
_buildDetailRow(
l10n.date, _enquiryData!['transactiondate']),
_buildDetailRow(
l10n.journalNo, _enquiryData!['journalno']),
],
),
),
),
],
),
),
);
}
Widget _buildDetailRow(String label, dynamic value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: const TextStyle(
fontWeight: FontWeight.w500, color: Colors.grey),
),
Flexible(
child: Text(
value?.toString() ?? AppLocalizations.of(context).notApplicable,
style: const TextStyle(fontWeight: FontWeight.bold),
textAlign: TextAlign.end,
),
),
],
),
);
}
}

View File

@@ -1,403 +0,0 @@
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/yojna_service.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class PMJJBYScreen extends StatefulWidget {
final Map<String, dynamic>? initialData;
const PMJJBYScreen({super.key, this.initialData});
@override
State<PMJJBYScreen> createState() => _PMJJBYScreenState();
}
class _PMJJBYScreenState extends State<PMJJBYScreen> {
final _formKey = GlobalKey<FormState>();
// Controllers for all requested fields
late final _aadhaarController =
TextEditingController(text: widget.initialData?['aadharno']?.toString());
late final _accountNoController =
TextEditingController(text: widget.initialData?['accountno']?.toString());
late final _balanceController = TextEditingController(
text: widget.initialData?['availablebalance']?.toString());
late final _countryController = TextEditingController(
text: widget.initialData?['country']?.toString() ?? 'IN');
late final _dobController = TextEditingController(
text: widget.initialData?['customerdob']?.toString());
late final _nameController = TextEditingController(
text: widget.initialData?['customername']?.toString());
late final _customerNoController = TextEditingController(
text: widget.initialData?['customerno']?.toString());
late final _acctOpeningDateController = TextEditingController(
text: widget.initialData?['dateofacctopening']?.toString());
late final _emailController =
TextEditingController(text: widget.initialData?['emailid']?.toString());
late final _financialYearController = TextEditingController(
text: widget.initialData?['financialyear']?.toString());
late final _genderController =
TextEditingController(text: widget.initialData?['gender']?.toString());
late final _ifscController =
TextEditingController(text: widget.initialData?['ifsccode']?.toString());
late final _marriedController =
TextEditingController(text: widget.initialData?['married']?.toString());
late final _mobileController =
TextEditingController(text: widget.initialData?['mobileno']?.toString());
late final _panController =
TextEditingController(text: widget.initialData?['pan']?.toString());
late final _pincodeController =
TextEditingController(text: widget.initialData?['pincode']?.toString());
late final _policyNumberController = TextEditingController(
text: widget.initialData?['policynumber']?.toString());
late final _premiumAmountController = TextEditingController(
text: widget.initialData?['premiumamount']?.toString());
late final _stateController =
TextEditingController(text: widget.initialData?['state']?.toString());
// Mapping options
final Map<String, String> _healthStatusOptions = {
'1': 'Excellent',
'2': 'Good',
'3': 'Bad',
};
final Map<String, String> _relationshipOptions = {
'01': 'Self',
'02': 'Wife',
'03': 'Father',
'04': 'Mother',
'05': 'Son',
'06': 'Daughter',
'07': 'Brother',
'08': 'Sister',
'09': 'Father-in-law',
'10': 'Mother-in-law',
'11': 'Grandson',
'12': 'Granddaughter',
'13': 'Grandfather',
'14': 'Grandmother',
'15': 'Brother-in-law',
'16': 'Sister-in-law',
'17': 'Husband',
'18': 'Guardian',
'99': 'Others',
};
final Map<String, String> _minorOptions = {
'Y': 'Yes',
'N': 'No',
};
final Map<String, String> _ruralOptions = {
'R': 'Rural',
'U': 'Urban',
'S': 'Semi-Urban',
'M': 'Metro',
};
final _healthStatusController = TextEditingController();
final _collectionChannelController = TextEditingController();
final _nomineeNameController = TextEditingController();
final _nomineeAddressController = TextEditingController();
final _nomineeRelationshipController = TextEditingController();
final _nomineeMinorController = TextEditingController();
final _ruralCategoryController = TextEditingController();
@override
void initState() {
super.initState();
// Initialize dropdown controllers if data exists in initialData
if (widget.initialData != null) {
if (widget.initialData!.containsKey('ruralcategory')) {
_ruralCategoryController.text =
widget.initialData!['ruralcategory'].toString();
}
if (widget.initialData!.containsKey('healthstatus')) {
_healthStatusController.text =
widget.initialData!['healthstatus'].toString();
}
if (widget.initialData!.containsKey('nomineerelationship')) {
_nomineeRelationshipController.text =
widget.initialData!['nomineerelationship'].toString();
}
if (widget.initialData!.containsKey('nomineeminor')) {
_nomineeMinorController.text =
widget.initialData!['nomineeminor'].toString();
}
}
}
@override
void dispose() {
_aadhaarController.dispose();
_accountNoController.dispose();
_balanceController.dispose();
_countryController.dispose();
_dobController.dispose();
_nameController.dispose();
_customerNoController.dispose();
_acctOpeningDateController.dispose();
_emailController.dispose();
_financialYearController.dispose();
_genderController.dispose();
_ifscController.dispose();
_marriedController.dispose();
_mobileController.dispose();
_panController.dispose();
_pincodeController.dispose();
_policyNumberController.dispose();
_premiumAmountController.dispose();
_stateController.dispose();
_healthStatusController.dispose();
_collectionChannelController.dispose();
_nomineeNameController.dispose();
_nomineeAddressController.dispose();
_nomineeRelationshipController.dispose();
_nomineeMinorController.dispose();
_ruralCategoryController.dispose();
super.dispose();
}
bool _isFetched(String key) {
return widget.initialData != null &&
widget.initialData!.containsKey(key) &&
widget.initialData![key]?.toString().isNotEmpty == true;
}
Future<void> _handleRegister() async {
final l10n = AppLocalizations.of(context);
// Show loading spinner
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const Center(child: CircularProgressIndicator()),
);
try {
final response = await getIt<YojnaService>().secondvalidationPMJJBY(
aadharno: _aadhaarController.text,
accountno: _accountNoController.text,
availablebalance: _balanceController.text,
country: _countryController.text,
customerdob: _dobController.text,
customername: _nameController.text,
customerno: _customerNoController.text,
dateofacctopening: _acctOpeningDateController.text,
emailid: _emailController.text,
financialyear: _financialYearController.text,
gender: _genderController.text,
ifsccode: _ifscController.text,
married: _marriedController.text,
mobileno: _mobileController.text,
pan: _panController.text,
pincode: _pincodeController.text,
policynumber: _policyNumberController.text,
premiumamount: _premiumAmountController.text,
state: _stateController.text,
healthstatus: _healthStatusController.text,
collectionchannel: _collectionChannelController.text,
nomineename: _nomineeNameController.text,
nomineeaddress: _nomineeAddressController.text,
nomineerelationship: _nomineeRelationshipController.text,
nomineeminor: _nomineeMinorController.text,
ruralcategory: _ruralCategoryController.text,
);
String x = response.toString();
if (x.contains('RECORD ALREADY EXISTS')) {
x = l10n.recordAlreadyExists;
}
if (mounted) {
// Close loading spinner
Navigator.pop(context);
// Show response dialog
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: Text(l10n.response),
content: SingleChildScrollView(child: Text(x)),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).popUntil((route) => route.isFirst);
},
child: Text(l10n.close),
),
],
),
);
}
} catch (e) {
if (mounted) {
Navigator.pop(context); // Close loading spinner
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.genericError(e.toString()))),
);
}
}
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(l10n.pmjjbyRegistration),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildTextField(_nameController, l10n.customerName,
readOnly: _isFetched('customername')),
_buildTextField(_customerNoController, l10n.customerNo,
readOnly: _isFetched('customerno')),
_buildTextField(_accountNoController, l10n.accountNumber,
keyboardType: TextInputType.number,
readOnly: _isFetched('accountno')),
_buildTextField(_balanceController, l10n.availableBalance,
keyboardType: TextInputType.number,
readOnly: _isFetched('availablebalance')),
_buildTextField(_aadhaarController, l10n.aadhaarNo,
keyboardType: TextInputType.number,
readOnly: _isFetched('aadharno')),
_buildTextField(_dobController, l10n.customerDobFormat,
readOnly: _isFetched('customerdob')),
_buildTextField(_genderController, l10n.gender,
readOnly: _isFetched('gender')),
_buildTextField(_marriedController, l10n.marriedYesNo,
readOnly: _isFetched('married')),
_buildTextField(_mobileController, l10n.mobileNumber,
keyboardType: TextInputType.phone,
readOnly: _isFetched('mobileno')),
_buildTextField(_emailController, 'Email ID',
keyboardType: TextInputType.emailAddress,
readOnly: _isFetched('emailid')),
_buildTextField(_panController, l10n.pan,
readOnly: _isFetched('pan')),
_buildTextField(_ifscController, l10n.ifscCode,
readOnly: _isFetched('ifsccode')),
_buildTextField(
_acctOpeningDateController, l10n.dateOfAcctOpening,
readOnly: _isFetched('dateofacctopening')),
_buildTextField(_pincodeController, l10n.pincode,
keyboardType: TextInputType.number,
readOnly: _isFetched('pincode')),
_buildTextField(_stateController, l10n.state,
readOnly: _isFetched('state')),
_buildTextField(_countryController, l10n.country,
readOnly: _isFetched('country')),
_buildDropdownField(
_ruralCategoryController, l10n.ruralCategory, _ruralOptions,
readOnly: _isFetched('ruralcategory')),
const Divider(height: 32),
Text(l10n.policyDetails,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildTextField(_policyNumberController, l10n.policyNumber,
readOnly: _isFetched('policynumber')),
_buildTextField(_premiumAmountController, l10n.premiumAmount,
keyboardType: TextInputType.number,
readOnly: _isFetched('premiumamount')),
_buildTextField(_financialYearController, l10n.financialYear,
readOnly: _isFetched('financialyear')),
_buildDropdownField(_healthStatusController, l10n.healthStatus,
_healthStatusOptions),
_buildTextField(
_collectionChannelController, l10n.collectionChannel),
const Divider(height: 32),
Text(l10n.nomineeDetails,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildTextField(_nomineeNameController, l10n.nomineeName),
_buildTextField(_nomineeAddressController, l10n.nomineeAddress),
_buildDropdownField(_nomineeRelationshipController,
l10n.nomineeRelationship, _relationshipOptions,
readOnly: _isFetched('nomineerelationship')),
_buildDropdownField(
_nomineeMinorController, l10n.nomineeMinor, _minorOptions,
readOnly: _isFetched('nomineeminor')),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _handleRegister,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
l10n.register,
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 32),
],
),
),
),
);
}
Widget _buildDropdownField(TextEditingController controller, String label,
Map<String, String> options,
{bool readOnly = false}) {
// Determine current value
String? currentValue =
options.containsKey(controller.text) ? controller.text : null;
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: DropdownButtonFormField<String>(
value: currentValue,
onChanged: readOnly
? null
: (newValue) {
setState(() {
controller.text = newValue ?? '';
});
},
decoration: InputDecoration(
labelText: label,
border: const OutlineInputBorder(),
contentPadding:
const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
),
items: options.entries.map((entry) {
return DropdownMenuItem<String>(
value: entry.key,
child: Text("${entry.key} - ${entry.value}"),
);
}).toList(),
),
);
}
Widget _buildTextField(TextEditingController controller, String label,
{TextInputType keyboardType = TextInputType.text,
bool readOnly = false}) {
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: TextFormField(
controller: controller,
readOnly: readOnly,
decoration: InputDecoration(
labelText: label,
border: const OutlineInputBorder(),
contentPadding:
const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
),
keyboardType: keyboardType,
),
);
}
}

View File

@@ -1,233 +0,0 @@
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/yojna_service.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class PMSBYEnquiryScreen extends StatefulWidget {
final String? cifNumber;
const PMSBYEnquiryScreen({
super.key,
required this.cifNumber,
});
@override
State<PMSBYEnquiryScreen> createState() => _PMSBYEnquiryScreenState();
}
class _PMSBYEnquiryScreenState extends State<PMSBYEnquiryScreen> {
String? _selectedFinancialYear;
bool _isLoading = false;
Map<String, dynamic>? _enquiryData;
String? _errorMessage;
final List<String> _financialYears = [
'2021-2022',
'2022-2023',
'2023-2024',
'2024-2025',
'2025-2026',
];
@override
void initState() {
super.initState();
_selectedFinancialYear = null;
}
Future<void> _fetchEnquiryData() async {
final l10n = AppLocalizations.of(context);
if (_selectedFinancialYear == null || widget.cifNumber == null) return;
setState(() {
_isLoading = true;
_enquiryData = null;
_errorMessage = null;
});
final formattedYear = _selectedFinancialYear!.replaceAll('-', '');
try {
final response = await getIt<YojnaService>().enquiry(
scheme: '01',
action: 'E',
financialyear: formattedYear,
customerno: widget.cifNumber,
);
setState(() {
if (response is Map<String, dynamic>) {
if (response['status'] == 'FAILED') {
_errorMessage = response['message'] ?? l10n.noRecordFound;
} else {
_enquiryData = response;
}
} else if (response is List && response.isNotEmpty) {
_enquiryData = response[0] as Map<String, dynamic>;
} else {
_errorMessage = l10n.noDataFoundYear;
}
});
} catch (e) {
setState(() {
_errorMessage = l10n.genericError(e.toString());
});
} finally {
setState(() {
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(l10n.pmsbyDetails),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.person, color: Colors.blue),
title: Text(l10n.cifNumber),
subtitle: Text(
widget.cifNumber ?? l10n.notApplicable,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
),
const SizedBox(height: 16),
DropdownButtonFormField<String>(
value: _selectedFinancialYear,
decoration: InputDecoration(
labelText: l10n.selectFinancialYear,
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.calendar_today),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 8),
),
items: _financialYears.map((String year) {
return DropdownMenuItem<String>(
value: year,
child: Text(year),
);
}).toList(),
onChanged: (String? newValue) {
setState(() {
_selectedFinancialYear = newValue;
});
_fetchEnquiryData();
},
),
],
),
),
),
const SizedBox(height: 20),
if (_isLoading)
const Center(
child: Padding(
padding: EdgeInsets.all(20.0),
child: CircularProgressIndicator(),
),
)
else if (_errorMessage != null)
Card(
color: Colors.red.shade50,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
_errorMessage!,
style: TextStyle(
color: Colors.red.shade700,
fontWeight: FontWeight.bold),
//textAlign: Center,
),
),
)
else if (_enquiryData != null)
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.schemeDetails,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
),
const Divider(),
_buildDetailRow(
l10n.customerName, _enquiryData!['customername']),
_buildDetailRow(
l10n.policyNumber,
_enquiryData!['policynumber'] ??
_enquiryData!['policyno']),
_buildDetailRow(
l10n.accountNumber, _enquiryData!['accountno']),
_buildDetailRow(
l10n.premiumAmount,
_enquiryData!['preimiumamount'] ??
_enquiryData!['premiumamount']),
_buildDetailRow(
l10n.nomineeName, _enquiryData!['nomineename']),
_buildDetailRow(
l10n.date, _enquiryData!['transactiondate']),
_buildDetailRow(
l10n.journalNo, _enquiryData!['journalno']),
],
),
),
),
],
),
),
);
}
Widget _buildDetailRow(String label, dynamic value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: const TextStyle(
fontWeight: FontWeight.w500, color: Colors.grey),
),
Flexible(
child: Text(
value?.toString() ?? AppLocalizations.of(context).notApplicable,
style: const TextStyle(fontWeight: FontWeight.bold),
textAlign: TextAlign.end,
),
),
],
),
);
}
}

View File

@@ -1,430 +0,0 @@
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/yojna_service.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class PMSBYScreen extends StatefulWidget {
final Map<String, dynamic>? initialData;
const PMSBYScreen({super.key, this.initialData});
@override
State<PMSBYScreen> createState() => _PMSBYScreenState();
}
class _PMSBYScreenState extends State<PMSBYScreen> {
final _formKey = GlobalKey<FormState>();
// Controllers for all requested fields
late final _aadhaarController =
TextEditingController(text: widget.initialData?['aadharno']?.toString());
late final _accountNoController =
TextEditingController(text: widget.initialData?['accountno']?.toString());
late final _balanceController = TextEditingController(
text: widget.initialData?['availablebalance']?.toString());
late final _countryController = TextEditingController(
text: widget.initialData?['country']?.toString() ?? 'IN');
late final _dobController = TextEditingController(
text: widget.initialData?['customerdob']?.toString());
late final _nameController = TextEditingController(
text: widget.initialData?['customername']?.toString());
late final _customerNoController = TextEditingController(
text: widget.initialData?['customerno']?.toString());
late final _acctOpeningDateController = TextEditingController(
text: widget.initialData?['dateofacctopening']?.toString());
late final _emailController =
TextEditingController(text: widget.initialData?['emailid']?.toString());
late final _financialYearController = TextEditingController(
text: widget.initialData?['financialyear']?.toString());
late final _genderController =
TextEditingController(text: widget.initialData?['gender']?.toString());
late final _ifscController =
TextEditingController(text: widget.initialData?['ifsccode']?.toString());
late final _marriedController =
TextEditingController(text: widget.initialData?['married']?.toString());
late final _mobileController =
TextEditingController(text: widget.initialData?['mobileno']?.toString());
late final _panController =
TextEditingController(text: widget.initialData?['pan']?.toString());
late final _pincodeController =
TextEditingController(text: widget.initialData?['pincode']?.toString());
late final _policyNumberController =
TextEditingController(text: widget.initialData?['policyno']?.toString());
late final _premiumAmountController = TextEditingController(
text: widget.initialData?['premiumamount']?.toString());
late final _stateController =
TextEditingController(text: widget.initialData?['state']?.toString());
late final _address1Controller =
TextEditingController(text: widget.initialData?['address1']?.toString());
late final _address2Controller =
TextEditingController(text: widget.initialData?['address2']?.toString());
late final _cityController =
TextEditingController(text: widget.initialData?['city']?.toString());
late final _relationWithNomineeController = TextEditingController(
text: widget.initialData?['relationwithnominee']?.toString());
late final _policyStatusController = TextEditingController(
text: widget.initialData?['policystatus']?.toString());
// Mapping options
final Map<String, String> _healthStatusOptions = {
'1': 'Excellent',
'2': 'Good',
'3': 'Bad',
};
final Map<String, String> _relationshipOptions = {
'01': 'Self',
'02': 'Wife',
'03': 'Father',
'04': 'Mother',
'05': 'Son',
'06': 'Daughter',
'07': 'Brother',
'08': 'Sister',
'09': 'Father-in-law',
'10': 'Mother-in-law',
'11': 'Grandson',
'12': 'Granddaughter',
'13': 'Grandfather',
'14': 'Grandmother',
'15': 'Brother-in-law',
'16': 'Sister-in-law',
'17': 'Husband',
'18': 'Guardian',
'99': 'Others',
};
final Map<String, String> _minorOptions = {
'Y': 'Yes',
'N': 'No',
};
final Map<String, String> _ruralOptions = {
'R': 'Rural',
'U': 'Urban',
'S': 'Semi-Urban',
'M': 'Metro',
};
final _healthStatusController = TextEditingController();
final _collectionChannelController = TextEditingController();
final _nomineeNameController = TextEditingController();
final _nomineeAddressController = TextEditingController();
final _nomineeRelationshipController = TextEditingController();
final _nomineeMinorController = TextEditingController();
final _ruralCategoryController = TextEditingController();
@override
void initState() {
super.initState();
// Initialize dropdown controllers if data exists in initialData
if (widget.initialData != null) {
if (widget.initialData!.containsKey('ruralcategory')) {
_ruralCategoryController.text =
widget.initialData!['ruralcategory'].toString();
}
if (widget.initialData!.containsKey('healthstatus')) {
_healthStatusController.text =
widget.initialData!['healthstatus'].toString();
}
if (widget.initialData!.containsKey('relationwithnominee')) {
_nomineeRelationshipController.text =
widget.initialData!['relationwithnominee'].toString();
}
if (widget.initialData!.containsKey('nomineeminor')) {
_nomineeMinorController.text =
widget.initialData!['nomineeminor'].toString();
}
}
}
@override
void dispose() {
_aadhaarController.dispose();
_accountNoController.dispose();
_balanceController.dispose();
_countryController.dispose();
_dobController.dispose();
_nameController.dispose();
_customerNoController.dispose();
_acctOpeningDateController.dispose();
_emailController.dispose();
_financialYearController.dispose();
_genderController.dispose();
_ifscController.dispose();
_marriedController.dispose();
_mobileController.dispose();
_panController.dispose();
_pincodeController.dispose();
_policyNumberController.dispose();
_premiumAmountController.dispose();
_stateController.dispose();
_address1Controller.dispose();
_address2Controller.dispose();
_cityController.dispose();
_relationWithNomineeController.dispose();
_policyStatusController.dispose();
_healthStatusController.dispose();
_collectionChannelController.dispose();
_nomineeNameController.dispose();
_nomineeAddressController.dispose();
_nomineeRelationshipController.dispose();
_nomineeMinorController.dispose();
_ruralCategoryController.dispose();
super.dispose();
}
bool _isFetched(String key) {
return widget.initialData != null &&
widget.initialData!.containsKey(key) &&
widget.initialData![key]?.toString().isNotEmpty == true;
}
Future<void> _handleRegister() async {
final l10n = AppLocalizations.of(context);
// Show loading spinner
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const Center(child: CircularProgressIndicator()),
);
try {
final response = await getIt<YojnaService>().secondvalidationPMSBY(
aadharno: _aadhaarController.text,
accountno: _accountNoController.text,
address1: _address1Controller.text,
address2: _address2Controller.text,
availablebalance: _balanceController.text,
city: _cityController.text,
country: _countryController.text,
customerdob: _dobController.text,
customername: _nameController.text,
customerno: _customerNoController.text,
emailid: _emailController.text,
financialyear: _financialYearController.text,
gender: _genderController.text,
married: _marriedController.text,
mobileno: _mobileController.text,
pan: _panController.text,
pincode: _pincodeController.text,
policyno: _policyNumberController.text,
premiumamount: _premiumAmountController.text,
state: _stateController.text,
healthstatus: _healthStatusController.text,
nomineename: _nomineeNameController.text,
nomineeadress: _nomineeAddressController.text,
relationwithnominee: _relationWithNomineeController.text,
nomineeminor: _nomineeMinorController.text,
collectionchannel: _collectionChannelController.text,
ruralcategory: _ruralCategoryController.text,
policystatus: _policyStatusController.text,
);
String x = response.toString();
if (x.contains('RECORD ALREADY EXISTS')) {
x = l10n.recordAlreadyExists;
}
if (mounted) {
// Close loading spinner
Navigator.pop(context);
// Show response dialog
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: Text(l10n.response),
content: SingleChildScrollView(child: Text(x)),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).popUntil((route) => route.isFirst);
},
child: Text(l10n.close),
),
],
),
);
}
} catch (e) {
if (mounted) {
Navigator.pop(context); // Close loading spinner
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.genericError(e.toString()))),
);
}
}
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(l10n.pmsbyRegistration),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildTextField(_nameController, l10n.customerName,
readOnly: _isFetched('customername')),
_buildTextField(_customerNoController, l10n.customerNo,
readOnly: _isFetched('customerno')),
_buildTextField(_accountNoController, l10n.accountNumber,
keyboardType: TextInputType.number,
readOnly: _isFetched('accountno')),
_buildTextField(_balanceController, l10n.availableBalance,
keyboardType: TextInputType.number,
readOnly: _isFetched('availablebalance')),
_buildTextField(_aadhaarController, l10n.aadhaarNo,
keyboardType: TextInputType.number,
readOnly: _isFetched('aadharno')),
_buildTextField(_dobController, l10n.customerDobFormat,
readOnly: _isFetched('customerdob')),
_buildTextField(_genderController, l10n.gender,
readOnly: _isFetched('gender')),
_buildTextField(_marriedController, l10n.marriedYesNo,
readOnly: _isFetched('married')),
_buildTextField(_mobileController, l10n.mobileNumber,
keyboardType: TextInputType.phone,
readOnly: _isFetched('mobileno')),
_buildTextField(_emailController, 'Email ID',
keyboardType: TextInputType.emailAddress,
readOnly: _isFetched('emailid')),
_buildTextField(_address1Controller, l10n.address1,
readOnly: _isFetched('address1')),
_buildTextField(_address2Controller, l10n.address2,
readOnly: _isFetched('address2')),
_buildTextField(_cityController, l10n.city,
readOnly: _isFetched('city')),
_buildTextField(_panController, l10n.pan,
readOnly: _isFetched('pan')),
_buildTextField(_ifscController, l10n.ifscCode,
readOnly: _isFetched('ifsccode')),
_buildTextField(
_acctOpeningDateController, l10n.dateOfAcctOpening,
readOnly: _isFetched('dateofacctopening')),
_buildTextField(_pincodeController, l10n.pincode,
keyboardType: TextInputType.number,
readOnly: _isFetched('pincode')),
_buildTextField(_stateController, l10n.state,
readOnly: _isFetched('state')),
_buildTextField(_countryController, l10n.country,
readOnly: _isFetched('country')),
_buildDropdownField(
_ruralCategoryController, l10n.ruralCategory, _ruralOptions,
readOnly: _isFetched('ruralcategory')),
const Divider(height: 32),
Text(l10n.policyDetails,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildTextField(_policyNumberController, l10n.policyNumber,
readOnly: _isFetched('policyno')),
_buildTextField(_premiumAmountController, l10n.premiumAmount,
keyboardType: TextInputType.number,
readOnly: _isFetched('premiumamount')),
_buildTextField(_financialYearController, l10n.financialYear,
readOnly: _isFetched('financialyear')),
_buildTextField(_policyStatusController, l10n.policyStatus,
readOnly: _isFetched('policystatus')),
_buildDropdownField(_healthStatusController, l10n.healthStatus,
_healthStatusOptions),
_buildTextField(
_collectionChannelController, l10n.collectionChannel),
const Divider(height: 32),
Text(l10n.nomineeDetails,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildTextField(_nomineeNameController, l10n.nomineeName),
_buildTextField(_nomineeAddressController, l10n.nomineeAddress),
_buildDropdownField(_relationWithNomineeController,
l10n.relationWithNominee, _relationshipOptions,
readOnly: _isFetched('relationwithnominee')),
_buildTextField(
_nomineeRelationshipController, l10n.nomineeRelationship),
_buildDropdownField(
_nomineeMinorController, l10n.nomineeMinor, _minorOptions,
readOnly: _isFetched('nomineeminor')),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _handleRegister,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
l10n.register,
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 32),
],
),
),
),
);
}
Widget _buildDropdownField(TextEditingController controller, String label,
Map<String, String> options,
{bool readOnly = false}) {
// Determine current value
String? currentValue =
options.containsKey(controller.text) ? controller.text : null;
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: DropdownButtonFormField<String>(
value: currentValue,
onChanged: readOnly
? null
: (newValue) {
setState(() {
controller.text = newValue ?? '';
});
},
decoration: InputDecoration(
labelText: label,
border: const OutlineInputBorder(),
contentPadding:
const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
),
items: options.entries.map((entry) {
return DropdownMenuItem<String>(
value: entry.key,
child: Text("${entry.key} - ${entry.value}"),
);
}).toList(),
),
);
}
Widget _buildTextField(TextEditingController controller, String label,
{TextInputType keyboardType = TextInputType.text,
bool readOnly = false}) {
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: TextFormField(
controller: controller,
readOnly: readOnly,
decoration: InputDecoration(
labelText: label,
border: const OutlineInputBorder(),
contentPadding:
const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
),
keyboardType: keyboardType,
),
);
}
}

View File

@@ -36,7 +36,7 @@
"accountStatement": "Account \n Statement",
"handleCheque": "Handle \n Cheque",
"manageBeneficiary": "Manage \n Beneficiary",
"contactUs": "Contact Us",
"contactUs": "Contact \n Us",
"addBeneficiary": "Add Beneficiary",
"confirmAccountNumber": "Confirm Account Number",
"name": "Name",
@@ -555,217 +555,5 @@
"stopChequeButton": "Stop Cheque",
"stopMultipleChequesTitle": "Stop Multiple Cheques",
"fromChequeNumberHint": "From Cheque Number *",
"toChequeNumberHint": "To Cheque Number *",
"failedToFetchChequeStatus": "Failed to fetch cheque status: {error}",
"revokeStopSubtitle": "Revoke your stopped cheques so as to reuse it",
"positivePaySubtitle": "Prevent unauthorized use of your issued cheque",
"positivePayTitle": "Positive Pay",
"pleaseEnterAccountNumber": "Please enter account number",
"chequeNumber": "Cheque Number",
"pleaseEnterChequeNumber": "Please enter cheque number",
"chequeIssuedDate": "Cheque Issued Date",
"pleaseSelectChequeIssuedDate": "Please select cheque issued date",
"payeeName": "Payee Name",
"pleaseEnterAmount": "Please enter the amount",
"processingData": "Processing Data",
"revokeStopCheque": "Revoke Stop Cheque",
"currentAccount": "Current Account",
"cashCreditAccount": "Cash Credit Account",
"revokeSingleStop": "Revoke Single Stop",
"noStoppedChequesPresent": "No stopped cheques present",
"revokeMultipleStops": "Revoke Multiple Stops",
"revokeSingleStopTitle": "Revoke Single Stop",
"chequeFound": "Cheque Found",
"chequeFixed": "Cheque Fixed",
"other": "Other",
"revokeIssueDate": "Revoke Issue Date",
"revokeExpiryDate": "Revoke Expiry Date",
"revokeAmount": "Revoke Amount",
"revokeComment": "Revoke Comment",
"otherReasons": "Other Reasons :",
"revokeStopButton": "Revoke Stop",
"invalidTpin": "Invalid TPIN",
"incorrectTpinMessage": "The TPIN you entered is incorrect. Please try again.",
"chequeAlreadyStopped": "The selected Cheque is already stopped",
"chequeAlreadyPresented": "The selected Cheque is already presented",
"chequeLost": "Cheque Lost",
"chequeStolen": "Cheque Stolen",
"chequeMissing": "Cheque Missing",
"chequeDamaged": "Cheque Damaged",
"close": "Close",
"governmentSchemes": "Government Schemes",
"pradhanMantriYojana": "Pradhan Mantri Yojana",
"atalPensionYojana": "Atal Pension Yojana",
"registerForAtalPensionYojana": "Register for Atal Pension Yojana",
"secureYourFutureAPY": "Secure your future with Atal Pension Yojana (APY) retirement scheme. Register in a few steps.",
"enrollPMJJBYPMSBY": "Enroll in Pradhan Mantri Jeevan Jyoti Bima Yojana (PMJJBY) and Pradhan Mantri Suraksha Bima Yojana (PMSBY) insurance schemes",
"apyRegistration": "APY registration",
"apyDescription": "Atal Pension Yojana (APY) is a periodic contribution-based pension scheme for citizens of India.",
"pmjjbyPmsbyDescription": "Create and Enquire about your Pradhan Mantri Jeevan Jyoti Bima Yojana (PMJJBY) and Pradhan Mantri Suraksha Bima Yojana(PMSBY) schemes.",
"selectScheme": "Select Scheme",
"pmjjbyFull": "Pradhan Mantri Jeevan Jyoti Bima Yojana (PMJJBY)",
"pmsbyFull": "Pradhan Mantri Suraksha Bima Yojana (PMSBY)",
"create": "Create",
"pleaseSelectAccountNumber": "Please select account number",
"pleaseSelectSchemeFirst": "Please select a scheme first",
"fetchingDetails": "Fetching details...",
"failedToFetchDetails": "Failed to fetch details or no data found.",
"pmjjbyDetails": "PMJJBY details",
"pmsbyDetails": "PMSBY details",
"cifNumber": "CIF Number",
"selectFinancialYear": "Select Financial Year",
"schemeDetails": "Scheme Details",
"customerName": "Customer Name",
"policyNumber": "Policy Number",
"premiumAmount": "Premium Amount",
"nomineeName": "Nominee Name",
"journalNo": "Journal No",
"noRecordFound": "No record found",
"noDataFoundYear": "No data found for the selected year.",
"pmjjbyRegistration": "PMJJBY Registration",
"pmsbyRegistration": "PMSBY Registration",
"customerNo": "Customer No",
"aadhaarNo": "Aadhaar No",
"customerDobFormat": "Customer DOB (DD/MM/YYYY)",
"gender": "Gender",
"marriedYesNo": "Married (Yes/No)",
"pan": "PAN",
"dateOfAcctOpening": "Date of Acct Opening",
"pincode": "Pincode",
"state": "State",
"country": "Country",
"ruralCategory": "Rural Category",
"policyDetails": "Policy Details",
"financialYear": "Financial Year",
"healthStatus": "Health Status",
"collectionChannel": "Collection Channel",
"nomineeDetails": "Nominee Details",
"nomineeAddress": "Nominee Address",
"relationWithNominee": "Relation with Nominee",
"nomineeRelationship": "Nominee Relationship",
"nomineeMinor": "Nominee Minor",
"response": "Response",
"recordAlreadyExists": "A record already exists for this request. Your submission has been registered previously.",
"address1": "Address 1",
"address2": "Address 2",
"city": "City",
"relationwithnominee": "Relation with Nominee",
"policyStatus": "Policy Status",
"emailId": "Email ID",
"customerFirstName": "Customer First Name",
"customerMiddleName": "Customer Middle Name",
"customerLastName": "Customer Last Name",
"pensionAmountOptedFor": "Pension Amount Opted For",
"monthlyContribution": "Monthly Contribution",
"ageOfJoining": "Age of Joining",
"nameOfSpouse": "Name of Spouse",
"isIncomeTaxPayer": "Whether Income Tax Payer",
"isBeneficiaryOtherScheme": "Beneficiary of other Social Security Schemes",
"relationWithSubscriber": "Relation with Subscriber",
"secondNomineeName": "Second Nominee Name",
"secondNomineeRelationship": "Second Nominee Relationship",
"secondNomineeMinor": "Second Nominee Minor",
"fatcaApplicable": "FATCA/CRS Applicable",
"countryOfBirth": "Country of Birth",
"countryOfCitizenship": "Country of Citizenship",
"countryOfTaxResidence": "Country of Residence for Tax Purpose",
"usPersonFlag": "US Person Flag",
"modeOfCollection": "Mode of Collection",
"contributionType": "Contribution Type",
"subsequentDebitDate": "Subsequent Contribution Debit Date",
"customerTitle": "Title",
"nomineeDob": "Nominee Date of Birth",
"guardianName": "Guardian's Name",
"pensionAmount": "Pension Amount",
"periodicity": "Periodicity",
"contributionAmount": "Contribution Amount",
"monthly": "Monthly",
"quarterly": "Quarterly",
"halfYearly": "Half Yearly",
"accountOpeningDescription": "Select your source account to proceed with opening a new FD, TD, or RD account. Your KYC details will be verified as part of this process. Please ensure all fields are filled accordingly to proceed. Once you have successfully entered your T-PIN and verification is complete, the amount will be debited from your selected source account. Your new account number will be generated instantly after the process is finalized. You will be able to see the complete details of your new account on the dashboard; furthermore, you can click on 'View All' or 'Account Info' to see comprehensive account information.",
"failedToFetchAccountDetails": "Failed to fetch account details",
"createDeposit": "Create Deposit",
"customerInformation": "Customer Information",
"fromAccountNo": "From Account No",
"cifNo": "CIF No",
"idNo": "ID No",
"nationality": "Nationality",
"addressLine1": "Address Line 1",
"addressLine2": "Address Line 2",
"pincodeLabel": "Pincode",
"depositDetails": "Deposit Details",
"product": "Product",
"type": "Type",
"customerCategory": "Customer Category",
"termLocation": "Term Location",
"currency": "Currency",
"accountSegmentCode": "Account Segment Code",
"interestTermSettings": "Interest & Term Settings",
"interestPaymentMethod": "Interest Payment Method",
"taxFileNumberIndicator": "Tax File Number Indicator",
"termLength": "Term Length",
"termBasis": "Term Basis",
"termValueDeposited": "Term Value Deposited",
"interestFrequency": "Interest Frequency",
"termDays": "Term Days",
"termMonths": "Term Months",
"termYears": "Term Years",
"nominationRequired": "Nomination Required",
"printNomineeName": "Print Nominee Name",
"recurringDepositSettings": "Recurring Deposit Settings",
"rdInstallmentValue": "RD Installment Value",
"monthlyRDInstallmentDueDay": "Monthly RD Installment Due Day",
"rdInstallmentFrequency": "RD Installment Frequency",
"creationFailed": "Creation Failed",
"accountCreationFailed": "Account creation could not be completed at this time.",
"reason": "Reason: {message}",
"creationFailedDescription": "Please ensure that the provided details (term length, basis, interest frequency, etc.) are correct and compatible with the selected product. If the issue persists, please contact the branch.",
"depositCreatedSuccessfully": "Deposit Created Successfully",
"amountValue": "Amount: {amount}",
"fieldRequired": "This field is required",
"unexpectedResponse": "Unexpected response from server",
"failedToCreateDeposit": "Failed to create deposit",
"termDepositOption": "Term Deposit",
"fixedDepositOption": "Fixed Deposit",
"recurringDepositOption": "Recurring Deposit",
"memberOption": "Member",
"nonMemberOption": "Non-Member",
"staffOption": "Staff",
"govtOption": "Govt",
"schoolOption": "School",
"panchayatOption": "Panchayat",
"trustsOption": "Trusts",
"municipalCouncilOption": "Municipal Council",
"nroOption": "NRO",
"bankOption": "Bank",
"noFrillOption": "No Frill",
"specialSchemesOption": "Special Schemes",
"minorOption": "Minor",
"publicIndividualOption": "Public Individual",
"societiesOption": "Societies",
"seniorCitizenOption": "Senior Citizen",
"governmentOption": "Government",
"localBodiesOption": "Local Bodies",
"otherOption": "Other",
"sbCaOption": "SB/CA",
"days46_90Option": "4690 Days",
"days91_180Option": "91180 Days",
"days180_1YearOption": "180 Days1 Year",
"year1_18MonthsOption": "1 Year<18 Months",
"months18_2YearsOption": "18 Months<2 Years",
"years2_3Option": "23 Years",
"years3_10Option": "310 Years",
"above10YearsOption": "Above 10 Years",
"devKanyaYojnaOption": "Dev Kanya Yojna 9+1",
"hazarDainLakhOption": "Hazar Dain Lakh Ley Jayain",
"glOthersOption": "GL Others",
"reInvestOption": "Re-invest",
"daysOption": "Days",
"monthsOption": "Months",
"onMaturityOption": "On Maturity / Roll Over",
"monthlyOption": "Monthly",
"quarterlyOption": "Quarterly",
"anniversaryMonthlyOption": "Anniversary Monthly",
"anniversaryQuarterlyOption": "Anniversary Quarterly",
"createnewdeposit" : "Create New Deposit"
}
"toChequeNumberHint": "To Cheque Number *"
}

View File

@@ -36,7 +36,7 @@
"accountStatement": "खाता \n विवरण",
"handleCheque": "चेक \n संभालें",
"manageBeneficiary": "लाभार्थी \n प्रबंधन",
"contactUs": "संपर्क करें",
"contactUs": "संपर्क \n करें",
"addBeneficiary": "लाभार्थी जोड़ें",
"confirmAccountNumber": "खाता संख्या की पुष्टि करें",
"name": "नाम",
@@ -556,217 +556,5 @@
"stopChequeButton": "चेक रोकें",
"stopMultipleChequesTitle": "एकाधिक चेक रोकें",
"fromChequeNumberHint": "चेक नंबर से *",
"toChequeNumberHint": "चेक नंबर तक *",
"failedToFetchChequeStatus": "चेक स्थिति लाने में विफल: {error}",
"revokeStopSubtitle": "अपने रोके गए चेकों को फिर से उपयोग करने के लिए निरस्त करें",
"positivePaySubtitle": "अनधिकृत उपयोग को रोकने के लिए अपने जारी किए गए चेक का विवरण जमा करें",
"positivePayTitle": "सकारात्मक वेतन",
"pleaseEnterAccountNumber": "कृपया खाता संख्या दर्ज करें",
"chequeNumber": "चेक संख्या",
"pleaseEnterChequeNumber": "कृपया चेक संख्या दर्ज करें",
"chequeIssuedDate": "चेक जारी करने की तारीख",
"pleaseSelectChequeIssuedDate": "कृपया चेक जारी करने की तारीख चुनें",
"payeeName": "प्राप्तकर्ता का नाम",
"pleaseEnterAmount": "कृपया राशि दर्ज करें",
"processingData": "डेटा संसाधित हो रहा है",
"revokeStopCheque": "चेक रोको रद्द करें",
"currentAccount": "चालू खाता",
"cashCreditAccount": "नगद श्रेय खाता",
"revokeSingleStop": "एकल रोक रद्द करें",
"noStoppedChequesPresent": "कोई रोका हुआ चेक मौजूद नहीं है",
"revokeMultipleStops": "कई रोक रद्द करें",
"revokeSingleStopTitle": "एकल रोक रद्द करें",
"chequeFound": "चेक मिला",
"chequeFixed": "चेक ठीक किया गया",
"other": "अन्य",
"revokeIssueDate": "रोक जारी करने की तारीख रद्द करें",
"revokeExpiryDate": "रोक समाप्ति तिथि रद्द करें",
"revokeAmount": "राशि रद्द करें",
"revokeComment": "टिप्पणी रद्द करें",
"otherReasons": "अन्य कारण :",
"revokeStopButton": "रोक रद्द करें",
"invalidTpin": "अमान्य टीपिन",
"incorrectTpinMessage": "आपके द्वारा दर्ज किया गया टीपिन गलत है। कृपया पुनः प्रयास करें।",
"chequeAlreadyStopped": "चुना हुआ चेक पहले से ही रोका हुआ है",
"chequeAlreadyPresented": "चुना हुआ चेक पहले से ही प्रस्तुत किया जा चुका है",
"chequeLost": "चेक खो गया",
"chequeStolen": "चेक चोरी हो गया",
"chequeMissing": "चेक गायब है",
"chequeDamaged": "चेक क्षतिग्रस्त है",
"close": "बंद करें",
"governmentSchemes": "सरकारी योजनाएं",
"pradhanMantriYojana": "प्रधानमंत्री योजना",
"atalPensionYojana": "अटल पेंशन योजना",
"registerForAtalPensionYojana": "अटल पेंशन योजना के लिए पंजीकरण करें",
"secureYourFutureAPY": "अटल पेंशन योजना (APY) सेवानिवृत्ति योजना के साथ अपना भविष्य सुरक्षित करें। कुछ ही चरणों में पंजीकरण करें।",
"enrollPMJJBYPMSBY": "प्रधानमंत्री जीवन ज्योति बीमा योजना (PMJJBY) और प्रधानमंत्री सुरक्षा बीमा योजना (PMSBY) बीमा योजनाओं में नामांकन करें",
"apyRegistration": "APY पंजीकरण",
"apyDescription": "अटल पेंशन योजना (APY) भारत के नागरिकों के लिए एक आवधिक योगदान-आधारित पेंशन योजना है।",
"pmjjbyPmsbyDescription": "अपनी प्रधानमंत्री जीवन ज्योति बीमा योजना (PMJJBY) और प्रधानमंत्री सुरक्षा बीमा योजना (PMSBY) योजनाओं के बारे में बनाएं और पूछताछ करें।",
"selectScheme": "योजना चुनें",
"pmjjbyFull": "प्रधानमंत्री जीवन ज्योति बीमा योजना (PMJJBY)",
"pmsbyFull": "प्रधानमंत्री सुरक्षा बीमा योजना (PMSBY)",
"create": "बनाएं",
"pleaseSelectAccountNumber": "कृपया खाता संख्या चुनें",
"pleaseSelectSchemeFirst": "कृपया पहले एक योजना चुनें",
"fetchingDetails": "विवरण प्राप्त किया जा रहा है...",
"failedToFetchDetails": "विवरण प्राप्त करने में विफल या कोई डेटा नहीं मिला।",
"pmjjbyDetails": "PMJJBY विवरण",
"pmsbyDetails": "PMSBY विवरण",
"cifNumber": "CIF संख्या",
"selectFinancialYear": "वित्तीय वर्ष चुनें",
"schemeDetails": "योजना विवरण",
"customerName": "ग्राहक का नाम",
"policyNumber": "पॉलिसी संख्या",
"premiumAmount": "प्रीमियम राशि",
"nomineeName": "नामांकित व्यक्ति का नाम",
"journalNo": "जर्नल नंबर",
"noRecordFound": "कोई रिकॉर्ड नहीं मिला",
"noDataFoundYear": "चयनित वर्ष के लिए कोई डेटा नहीं मिला।",
"pmjjbyRegistration": "PMJJBY पंजीकरण",
"pmsbyRegistration": "PMSBY पंजीकरण",
"customerNo": "ग्राहक संख्या",
"aadhaarNo": "आधार संख्या",
"customerDobFormat": "ग्राहक की जन्म तिथि (DD/MM/YYYY)",
"gender": "लिंग",
"marriedYesNo": "विवाहित (हाँ/नहीं)",
"pan": "पैन",
"dateOfAcctOpening": "खाता खोलने की तिथि",
"pincode": "पिनकोड",
"state": "राज्य",
"country": "देश",
"ruralCategory": "ग्रामीण श्रेणी",
"policyDetails": "पॉलिसी विवरण",
"financialYear": "वित्तीय वर्ष",
"healthStatus": "स्वास्थ्य की स्थिति",
"collectionChannel": "संग्रह चैनल",
"nomineeDetails": "नामांकित व्यक्ति का विवरण",
"nomineeAddress": "नामांकित व्यक्ति का पता",
"nomineeRelationship": "नामांकित व्यक्ति से संबंध",
"nomineeMinor": "नामांकित व्यक्ति नाबालिग है",
"response": "प्रतिक्रिया",
"recordAlreadyExists": "इस अनुरोध के लिए एक रिकॉर्ड पहले से मौजूद है। आपका सबमिशन पहले पंजीकृत किया जा चुका है।",
"address1": "पता 1",
"address2": "पता 2",
"city": "शहर",
"relationwithnominee": "नामांकित व्यक्ति के साथ संबंध",
"policyStatus": "पॉलिसी की स्थिति",
"emailId": "ईमेल आईडी",
"customerFirstName": "ग्राहक का पहला नाम",
"customerMiddleName": "ग्राहक का मध्य नाम",
"customerLastName": "ग्राहक का अंतिम नाम",
"pensionAmountOptedFor": "चुनी गई पेंशन राशि",
"monthlyContribution": "मासिक योगदान",
"ageOfJoining": "शामिल होने की आयु",
"nameOfSpouse": "पति/पत्नी का नाम",
"isIncomeTaxPayer": "क्या आयकर दाता है",
"isBeneficiaryOtherScheme": "अन्य सामाजिक सुरक्षा योजनाओं के लाभार्थी",
"relationWithSubscriber": "अभिदाता के साथ संबंध",
"secondNomineeName": "दूसरे नामांकित व्यक्ति का नाम",
"relationWithNominee": "नामांकित व्यक्ति के साथ संबंध",
"secondNomineeRelationship": "दूसरे नामांकित व्यक्ति का संबंध",
"secondNomineeMinor": "दूसरा नामांकित व्यक्ति नाबालिग है",
"fatcaApplicable": "FATCA/CRS लागू है",
"countryOfBirth": "जन्म का देश",
"countryOfCitizenship": "नागरिकता का देश",
"countryOfTaxResidence": "कर उद्देश्य के लिए निवास का देश",
"usPersonFlag": "अमेरिकी व्यक्ति ध्वज",
"modeOfCollection": "संग्रह का माध्यम",
"contributionType": "योगदान का प्रकार",
"subsequentDebitDate": "अगली योगदान डेबिट तिथि",
"customerTitle": "शीर्षक",
"nomineeDob": "नामांकित व्यक्ति की जन्म तिथि",
"guardianName": "अभिभावक का नाम",
"pensionAmount": "पेंशन राशि",
"periodicity": "आवधिकता",
"contributionAmount": "योगदान राशि",
"monthly": "मासिक",
"quarterly": "त्रैमासिक",
"halfYearly": "अर्धवार्षिक",
"accountOpeningDescription": "नया एफडी, टीडी या आरडी खाता खोलने के लिए अपना स्रोत खाता चुनें। इस प्रक्रिया के हिस्से के रूप में आपके केवाईसी विवरण सत्यापित किए जाएंगे। कृपया आगे बढ़ने के लिए सभी फ़ील्ड के अनुसार भरें। एक बार जब आप सफलतापूर्वक अपना टी-पिन दर्ज कर लेते हैं और सत्यापन पूरा हो जाता है, तो राशि आपके चुने हुए स्रोत खाते से काट ली जाएगी। प्रक्रिया पूरी होने के बाद आपका नया खाता नंबर तुरंत जनरेट हो जाएगा। आप डैशबोर्ड पर अपने नए खाते का पूरा विवरण देख पाएंगे; इसके अलावा, व्यापक खाता जानकारी देखने के लिए आप 'सभी देखें' या 'खाता जानकारी' पर क्लिक कर सकते हैं।",
"failedToFetchAccountDetails": "खाता विवरण प्राप्त करने में विफल",
"createDeposit": "जमा बनाएं",
"customerInformation": "ग्राहक जानकारी",
"fromAccountNo": "खाता नंबर से",
"cifNo": "सीआईएफ नंबर",
"idNo": "पहचान संख्या",
"nationality": "राष्ट्रीयता",
"addressLine1": "पता पंक्ति 1",
"addressLine2": "पता पंक्ति 2",
"pincodeLabel": "पिनकोड",
"depositDetails": "जमा विवरण",
"product": "उत्पाद",
"type": "प्रकार",
"customerCategory": "ग्राहक श्रेणी",
"termLocation": "अवधि स्थान",
"currency": "मुद्रा",
"accountSegmentCode": "खाता खंड कोड",
"interestTermSettings": "ब्याज और अवधि सेटिंग्स",
"interestPaymentMethod": "ब्याज भुगतान विधि",
"taxFileNumberIndicator": "टैक्स फाइल नंबर संकेतक",
"termLength": "अवधि की लंबाई",
"termBasis": "अवधि आधार",
"termValueDeposited": "जमा की गई अवधि मूल्य",
"interestFrequency": "ब्याज आवृत्ति",
"termDays": "अवधि दिन",
"termMonths": "अवधि महीने",
"termYears": "अवधि वर्ष",
"nominationRequired": "नामांकन आवश्यक",
"printNomineeName": "नामांकित व्यक्ति का नाम प्रिंट करें",
"recurringDepositSettings": "आवर्ती जमा सेटिंग्स",
"rdInstallmentValue": "आरडी किस्त मूल्य",
"monthlyRDInstallmentDueDay": "मासिक आरडी किस्त देय दिन",
"rdInstallmentFrequency": "आरडी किस्त आवृत्ति",
"creationFailed": "निर्माण विफल",
"accountCreationFailed": "इस समय खाता निर्माण पूरा नहीं किया जा सका।",
"reason": "कारण: {message}",
"creationFailedDescription": "कृपया सुनिश्चित करें कि प्रदान किए गए विवरण (अवधि की लंबाई, आधार, ब्याज आवृत्ति, आदि) सही हैं और चयनित उत्पाद के साथ संगत हैं। यदि समस्या बनी रहती है, तो कृपया शाखा से संपर्क करें।",
"depositCreatedSuccessfully": "जमा सफलतापूर्वक बनाया गया",
"amountValue": "राशि: {amount}",
"fieldRequired": "यह फ़ील्ड आवश्यक है",
"unexpectedResponse": "सर्वर से अप्रत्याशित प्रतिक्रिया",
"failedToCreateDeposit": "जमा बनाने में विफल",
"termDepositOption": "मियादी जमा",
"fixedDepositOption": "सावधि जमा",
"recurringDepositOption": "आवर्ती जमा",
"memberOption": "सदस्य",
"nonMemberOption": "गैर-सदस्य",
"staffOption": "कर्मचारी",
"govtOption": "सरकार",
"schoolOption": "स्कूल",
"panchayatOption": "पंचायत",
"trustsOption": "ट्रस्ट",
"municipalCouncilOption": "नगर परिषद",
"nroOption": "एनआरओ",
"bankOption": "बैंक",
"noFrillOption": "नो फ्रिल",
"specialSchemesOption": "विशेष योजनाएं",
"minorOption": "नाबालिग",
"publicIndividualOption": "सार्वजनिक व्यक्तिगत",
"societiesOption": "सोसायटी",
"seniorCitizenOption": "वरिष्ठ नागरिक",
"governmentOption": "सरकार",
"localBodiesOption": "स्थानीय निकाय",
"otherOption": "अन्य",
"sbCaOption": "बचत/चालू",
"days46_90Option": "46-90 दिन",
"days91_180Option": "91-180 दिन",
"days180_1YearOption": "180 दिन-1 वर्ष",
"year1_18MonthsOption": "1 वर्ष-<18 महीने",
"months18_2YearsOption": "18 महीने-<2 वर्ष",
"years2_3Option": "2-3 वर्ष",
"years3_10Option": "3-10 वर्ष",
"above10YearsOption": "10 वर्ष से अधिक",
"devKanyaYojnaOption": "देव कन्या योजना 9+1",
"hazarDainLakhOption": "हज़ार दें लाख ले जाएं",
"glOthersOption": "जीएल अन्य",
"reInvestOption": "पुनः निवेश",
"daysOption": "दिन",
"monthsOption": "महीने",
"onMaturityOption": "परिपक्वता पर / रोल ओवर",
"monthlyOption": "मासिक",
"quarterlyOption": "त्रैमासिक",
"anniversaryMonthlyOption": "वर्षगांठ मासिक",
"anniversaryQuarterlyOption": "वर्षगांठ त्रैमासिक",
"createnewdeposit" : "नया डिपॉजिट बनाएं"
}
"toChequeNumberHint": "चेक नंबर तक *"
}

View File

@@ -15,7 +15,7 @@ void main() async {
]);
// Check for device compromise
// final compromisedMessage = await SecurityService.deviceCompromisedMessage;
// final compromisedMessage = await SecurityService.deviceCompromisedMessage;
// if (compromisedMessage != null) {
// runApp(MaterialApp(
// home: SecurityErrorScreen(message: compromisedMessage),

View File

@@ -69,10 +69,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.3.0"
checked_yaml:
dependency: transitive
description:
@@ -93,18 +93,18 @@ packages:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted
version: "1.1.2"
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.19.1"
version: "1.18.0"
confetti:
dependency: "direct main"
description:
@@ -189,10 +189,10 @@ packages:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
version: "1.3.1"
ffi:
dependency: transitive
description:
@@ -425,10 +425,10 @@ packages:
dependency: "direct main"
description:
name: intl
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.dev"
source: hosted
version: "0.20.2"
version: "0.19.0"
jailbreak_root_detection:
dependency: "direct main"
description:
@@ -457,26 +457,26 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
version: "10.0.5"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
url: "https://pub.dev"
source: hosted
version: "3.0.10"
version: "3.0.5"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
version: "3.0.1"
lints:
dependency: transitive
description:
@@ -537,10 +537,10 @@ packages:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.17"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
@@ -561,10 +561,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
version: "1.16.0"
version: "1.15.0"
mime:
dependency: transitive
description:
@@ -609,10 +609,10 @@ packages:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.9.0"
path_parsing:
dependency: transitive
description:
@@ -765,14 +765,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.2"
qr_flutter:
dependency: "direct main"
description:
name: qr_flutter
sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097"
url: "https://pub.dev"
source: hosted
version: "4.1.0"
screenshot:
dependency: "direct main"
description:
@@ -781,6 +773,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.0"
send_message:
dependency: "direct main"
description:
name: send_message
sha256: "79b5f69fd3ab0b9e6265f8d972800d7989b3082a0523c7f4b8e38bf4e1c71235"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
share_plus:
dependency: "direct main"
description:
@@ -869,11 +869,19 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
simcards:
dependency: "direct main"
description:
name: simcards
sha256: b621cc265ebbb3e11009ca9be67063efbc011396c4224aff8b08edaba30fa5ae
url: "https://pub.dev"
source: hosted
version: "0.0.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
version: "0.0.99"
source_span:
dependency: transitive
description:
@@ -894,18 +902,18 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.4"
version: "2.1.2"
string_scanner:
dependency: transitive
description:
@@ -926,10 +934,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
url: "https://pub.dev"
source: hosted
version: "0.7.6"
version: "0.7.2"
timezone:
dependency: transitive
description:
@@ -1011,7 +1019,7 @@ packages:
source: hosted
version: "3.1.4"
uuid:
dependency: transitive
dependency: "direct main"
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
@@ -1046,10 +1054,10 @@ packages:
dependency: transitive
description:
name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.1.4"
vm_service:
dependency: transitive
description:
@@ -1107,5 +1115,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.8.0-0 <4.0.0"
dart: ">=3.5.0 <4.0.0"
flutter: ">=3.24.0"

View File

@@ -34,7 +34,7 @@ dependencies:
cupertino_icons: ^1.0.6
jailbreak_root_detection: ^1.1.6
equatable: ^2.0.7
dio: ^5.8.0+1
dio: ^5.9.0
flutter_secure_storage: ^9.2.4
bloc: ^9.0.0
flutter_bloc: ^9.1.0
@@ -63,7 +63,9 @@ dependencies:
package_info_plus: ^4.2.0
flutter_local_notifications: ^19.5.0
open_filex: ^4.7.0
qr_flutter: ^4.1.0
simcards: ^0.0.1
uuid: ^4.5.1
send_message: ^1.0.0
# jailbreak_root_detection: "^1.1.6"

View File

@@ -1,498 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';
import 'package:kmobile/data/models/transaction.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/data/repositories/transaction_repository.dart';
import 'package:kmobile/features/accounts/screens/account_statement_screen.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:kmobile/l10n/app_localizations.dart';
// ─── Mock TransactionRepository ──────────────────────────────────────────────
class MockTransactionRepository implements TransactionRepository {
List<Transaction> mockTransactions = [];
bool shouldThrow = false;
String? lastAccountNo;
DateTime? lastFromDate;
DateTime? lastToDate;
int callCount = 0;
@override
Future<List<Transaction>> fetchTransactions(
String accountNo, {
DateTime? fromDate,
DateTime? toDate,
}) async {
callCount++;
lastAccountNo = accountNo;
lastFromDate = fromDate;
lastToDate = toDate;
if (shouldThrow) {
throw Exception('Network error');
}
return mockTransactions;
}
}
// ─── Test Helpers ────────────────────────────────────────────────────────────
final getIt = GetIt.instance;
User _createTestUser({
String accountNo = '1234567890',
String name = 'Test User',
String availableBalance = '50000',
String branchId = 'BR001',
String cifNumber = 'CIF123',
String address = '123 Main Street',
}) {
return User(
accountNo: accountNo,
accountType: 'SB',
branchId: branchId,
currency: 'INR',
availableBalance: availableBalance,
currentBalance: availableBalance,
name: name,
mobileNo: '9876543210',
address: address,
picode: '176215',
cifNumber: cifNumber,
);
}
Transaction _createTestTransaction({
String id = '1',
String name = 'UPI Payment',
String date = '01-01-2026',
String amount = '1000',
String type = 'DR',
String balance = '49000',
String balanceType = 'CR',
}) {
return Transaction(
id: id,
name: name,
date: date,
amount: amount,
type: type,
balance: balance,
balanceType: balanceType,
);
}
/// Wraps the widget with MaterialApp + localizations for testing
Widget _buildTestApp({
required List<User> users,
int selectedIndex = 0,
}) {
return MaterialApp(
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('en')],
locale: const Locale('en'),
home: AccountStatementScreen(
users: users,
selectedIndex: selectedIndex,
),
);
}
// ─── Tests ───────────────────────────────────────────────────────────────────
void main() {
late MockTransactionRepository mockRepo;
late User testUser;
late User testUser2;
setUp(() {
// Reset GetIt before each test
getIt.reset();
mockRepo = MockTransactionRepository();
getIt.registerSingleton<TransactionRepository>(mockRepo);
testUser = _createTestUser();
testUser2 = _createTestUser(
accountNo: '9876543210',
name: 'Second User',
availableBalance: '75000',
);
});
tearDown(() {
getIt.reset();
});
// ═══════════════════════════════════════════════════════════════════════════
// GROUP 1: Screen Rendering
// ═══════════════════════════════════════════════════════════════════════════
group('Screen Rendering', () {
testWidgets('renders AppBar with "Account Statement" title',
(tester) async {
await tester.pumpWidget(_buildTestApp(users: [testUser]));
await tester.pumpAndSettle();
expect(find.text('Account Statement'), findsOneWidget);
});
testWidgets('renders account number card with label', (tester) async {
await tester.pumpWidget(_buildTestApp(users: [testUser]));
await tester.pumpAndSettle();
expect(find.text('Account Number'), findsOneWidget);
});
testWidgets('renders available balance card', (tester) async {
await tester.pumpWidget(_buildTestApp(users: [testUser]));
await tester.pumpAndSettle();
// The balance text shows ' ₹ 50000'
expect(find.textContaining('50000'), findsOneWidget);
});
testWidgets('renders From Date and To Date filter boxes', (tester) async {
await tester.pumpWidget(_buildTestApp(users: [testUser]));
await tester.pumpAndSettle();
expect(find.text('From Date'), findsOneWidget);
expect(find.text('To Date'), findsOneWidget);
});
testWidgets('renders Search button', (tester) async {
await tester.pumpWidget(_buildTestApp(users: [testUser]));
await tester.pumpAndSettle();
expect(find.text('Search'), findsOneWidget);
});
testWidgets('renders floating action button for PDF download',
(tester) async {
await tester.pumpWidget(_buildTestApp(users: [testUser]));
await tester.pumpAndSettle();
expect(find.byType(FloatingActionButton), findsOneWidget);
expect(find.byIcon(Icons.download), findsOneWidget);
});
testWidgets('renders watermark logo with low opacity', (tester) async {
await tester.pumpWidget(_buildTestApp(users: [testUser]));
await tester.pumpAndSettle();
// There should be an Opacity widget wrapping the logo
final opacityFinder = find.byWidgetPredicate(
(widget) => widget is Opacity && widget.opacity == 0.07,
);
expect(opacityFinder, findsOneWidget);
});
});
// ═══════════════════════════════════════════════════════════════════════════
// GROUP 2: Account Dropdown
// ═══════════════════════════════════════════════════════════════════════════
group('Account Dropdown', () {
testWidgets('displays selected user account number in dropdown',
(tester) async {
await tester.pumpWidget(_buildTestApp(users: [testUser, testUser2]));
await tester.pumpAndSettle();
expect(find.text('1234567890'), findsOneWidget);
});
testWidgets('dropdown shows all user accounts when tapped', (tester) async {
await tester.pumpWidget(_buildTestApp(users: [testUser, testUser2]));
await tester.pumpAndSettle();
// Tap the dropdown
await tester.tap(find.byType(DropdownButton<User>));
await tester.pumpAndSettle();
// Both accounts should be visible in the dropdown menu
expect(find.text('1234567890'), findsWidgets);
expect(find.text('9876543210'), findsWidgets);
});
testWidgets('selecting another account triggers transaction reload',
(tester) async {
await tester.pumpWidget(_buildTestApp(users: [testUser, testUser2]));
await tester.pumpAndSettle();
final initialCallCount = mockRepo.callCount;
// Open dropdown and select second user
await tester.tap(find.byType(DropdownButton<User>));
await tester.pumpAndSettle();
// Tap the second account (appears in the overlay)
await tester.tap(find.text('9876543210').last);
await tester.pumpAndSettle();
// Verify repository was called again with the new account
expect(mockRepo.callCount, greaterThan(initialCallCount));
expect(mockRepo.lastAccountNo, '9876543210');
});
testWidgets('balance updates when user is switched', (tester) async {
await tester.pumpWidget(_buildTestApp(users: [testUser, testUser2]));
await tester.pumpAndSettle();
expect(find.textContaining('50000'), findsOneWidget);
// Switch to second user
await tester.tap(find.byType(DropdownButton<User>));
await tester.pumpAndSettle();
await tester.tap(find.text('9876543210').last);
await tester.pumpAndSettle();
// Balance should now show second user's balance
expect(find.textContaining('75000'), findsOneWidget);
});
});
// ═══════════════════════════════════════════════════════════════════════════
// GROUP 3: Loading State
// ═══════════════════════════════════════════════════════════════════════════
group('Loading State', () {
testWidgets('shows shimmer loading effect initially', (tester) async {
// Make the repo slow so loading state is visible
mockRepo = MockTransactionRepository();
getIt.allowReassignment = true;
getIt.registerSingleton<TransactionRepository>(mockRepo);
await tester.pumpWidget(_buildTestApp(users: [testUser]));
// Don't call pumpAndSettle — we want to see the loading state
await tester.pump();
// Check for shimmer placeholders (CircleAvatar is used in loading tiles)
expect(find.byType(CircleAvatar), findsWidgets);
});
});
// ═══════════════════════════════════════════════════════════════════════════
// GROUP 4: Empty State
// ═══════════════════════════════════════════════════════════════════════════
group('Empty State', () {
testWidgets('shows "No Transactions" when list is empty', (tester) async {
mockRepo.mockTransactions = [];
await tester.pumpWidget(_buildTestApp(users: [testUser]));
await tester.pumpAndSettle();
expect(find.text('No Transactions'), findsOneWidget);
});
});
// ═══════════════════════════════════════════════════════════════════════════
// GROUP 5: Transaction List
// ═══════════════════════════════════════════════════════════════════════════
group('Transaction List', () {
testWidgets('displays transaction list when data is loaded',
(tester) async {
mockRepo.mockTransactions = [
_createTestTransaction(
name: 'UPI Payment',
date: '01-01-2026',
amount: '1000',
type: 'DR',
balance: '49000',
),
_createTestTransaction(
id: '2',
name: 'Salary Credit',
date: '02-01-2026',
amount: '50000',
type: 'CR',
balance: '99000',
),
];
await tester.pumpWidget(_buildTestApp(users: [testUser]));
await tester.pumpAndSettle();
expect(find.text('01-01-2026'), findsOneWidget);
expect(find.text('02-01-2026'), findsOneWidget);
expect(find.text('UPI Payment'), findsOneWidget);
expect(find.text('Salary Credit'), findsOneWidget);
});
testWidgets('displays amounts with rupee symbol', (tester) async {
mockRepo.mockTransactions = [
_createTestTransaction(amount: '1500', balance: '48500'),
];
await tester.pumpWidget(_buildTestApp(users: [testUser]));
await tester.pumpAndSettle();
expect(find.text('₹1500'), findsOneWidget);
expect(find.text('Bal: ₹48500'), findsOneWidget);
});
testWidgets('truncates long transaction names to 22 characters',
(tester) async {
mockRepo.mockTransactions = [
_createTestTransaction(
name: 'This is a very long transaction name exceeding limit',
),
];
await tester.pumpWidget(_buildTestApp(users: [testUser]));
await tester.pumpAndSettle();
// Only first 22 characters should be displayed
expect(find.text('This is a very long tr'), findsOneWidget);
});
testWidgets('shows "Last 10 Transactions" label when no date filter set',
(tester) async {
mockRepo.mockTransactions = [
_createTestTransaction(),
];
await tester.pumpWidget(_buildTestApp(users: [testUser]));
await tester.pumpAndSettle();
expect(find.text('Last 10 Transactions'), findsOneWidget);
});
testWidgets('credit transactions show green icon', (tester) async {
mockRepo.mockTransactions = [
_createTestTransaction(type: 'CR'),
];
await tester.pumpWidget(_buildTestApp(users: [testUser]));
await tester.pumpAndSettle();
// Find the Icon for credit transactions (call_received)
final iconFinder = find.byWidgetPredicate(
(widget) => widget is Icon && widget.color == const Color(0xFF10BB10),
);
expect(iconFinder, findsOneWidget);
});
});
// ═══════════════════════════════════════════════════════════════════════════
// GROUP 6: Error State
// ═══════════════════════════════════════════════════════════════════════════
group('Error State', () {
testWidgets('shows snackbar on transaction load failure', (tester) async {
mockRepo.shouldThrow = true;
await tester.pumpWidget(_buildTestApp(users: [testUser]));
await tester.pumpAndSettle();
expect(find.byType(SnackBar), findsOneWidget);
expect(
find.textContaining('Failed to load transactions'), findsOneWidget);
});
});
// ═══════════════════════════════════════════════════════════════════════════
// GROUP 7: Date Filters
// ═══════════════════════════════════════════════════════════════════════════
group('Date Filters', () {
testWidgets('tapping "From Date" opens a date picker', (tester) async {
await tester.pumpWidget(_buildTestApp(users: [testUser]));
await tester.pumpAndSettle();
await tester.tap(find.text('From Date'));
await tester.pumpAndSettle();
// The DatePicker dialog should be open
expect(find.byType(DatePickerDialog), findsOneWidget);
});
testWidgets('tapping "To Date" without From Date shows error snackbar',
(tester) async {
await tester.pumpWidget(_buildTestApp(users: [testUser]));
await tester.pumpAndSettle();
await tester.tap(find.text('To Date'));
await tester.pumpAndSettle();
// Should show snackbar asking to select From Date first
expect(find.byType(SnackBar), findsOneWidget);
});
});
// ═══════════════════════════════════════════════════════════════════════════
// GROUP 8: Search Button
// ═══════════════════════════════════════════════════════════════════════════
group('Search Button', () {
testWidgets('pressing Search reloads transactions', (tester) async {
await tester.pumpWidget(_buildTestApp(users: [testUser]));
await tester.pumpAndSettle();
final callCountBefore = mockRepo.callCount;
await tester.tap(find.text('Search'));
await tester.pumpAndSettle();
expect(mockRepo.callCount, greaterThan(callCountBefore));
});
});
// ═══════════════════════════════════════════════════════════════════════════
// GROUP 9: PDF Export
// ═══════════════════════════════════════════════════════════════════════════
group('PDF Export', () {
testWidgets('pressing download FAB with no transactions shows snackbar',
(tester) async {
mockRepo.mockTransactions = [];
await tester.pumpWidget(_buildTestApp(users: [testUser]));
await tester.pumpAndSettle();
await tester.tap(find.byType(FloatingActionButton));
await tester.pumpAndSettle();
expect(find.text('No transactions to export.'), findsOneWidget);
});
});
// ═══════════════════════════════════════════════════════════════════════════
// GROUP 10: Selected Index
// ═══════════════════════════════════════════════════════════════════════════
group('Selected Index', () {
testWidgets('uses selectedIndex to pick initial user', (tester) async {
await tester.pumpWidget(
_buildTestApp(users: [testUser, testUser2], selectedIndex: 1),
);
await tester.pumpAndSettle();
// Second user's balance should be shown
expect(find.textContaining('75000'), findsOneWidget);
// Repository should have been called with second user's account
expect(mockRepo.lastAccountNo, '9876543210');
});
});
// ═══════════════════════════════════════════════════════════════════════════
// GROUP 11: Navigation
// ═══════════════════════════════════════════════════════════════════════════
group('Navigation', () {
testWidgets('tapping a transaction navigates to details screen',
(tester) async {
mockRepo.mockTransactions = [
_createTestTransaction(name: 'Test Payment'),
];
await tester.pumpWidget(_buildTestApp(users: [testUser]));
await tester.pumpAndSettle();
// Tap on the transaction ListTile
await tester.tap(find.text('Test Payment'));
await tester.pumpAndSettle();
// We should have navigated away from the statement screen
// The TransactionDetailsScreen should now be visible
expect(find.byType(AccountStatementScreen), findsNothing);
});
});
}