Compare commits
14 Commits
new-ui
...
sms-testin
| Author | SHA1 | Date | |
|---|---|---|---|
| 6861a4a349 | |||
| 298c0c199f | |||
| dfdc293309 | |||
| 1da7574ddb | |||
| 30a45015d0 | |||
| d8ebd0ed0e | |||
| 89569ab1c3 | |||
| 5c959ba15c | |||
| d89a4f5109 | |||
| 1c3a07bd66 | |||
| d44ee5590e | |||
| 715162b713 | |||
| 8149ef2a5b | |||
| 1a2dea611b |
@@ -2,6 +2,8 @@
|
|||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
|
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
|
||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<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
|
<application
|
||||||
android:label="kmobile"
|
android:label="kmobile"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
|||||||
@@ -8,6 +8,41 @@ class AuthService {
|
|||||||
final Dio _dio;
|
final Dio _dio;
|
||||||
AuthService(this._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 {
|
Future<AuthToken> login(AuthCredentials credentials) async {
|
||||||
try {
|
try {
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
|
|||||||
@@ -148,4 +148,5 @@ class BeneficiaryService {
|
|||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class BranchService {
|
|||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return Branch.listFromJson(response.data);
|
return Branch.listFromJson(response.data);
|
||||||
} else {
|
} else {
|
||||||
throw Exception("Failed to fetch");
|
throw Exception("Failed to fetch beneficiaries");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
102
lib/api/services/send_sms_service.dart
Normal file
102
lib/api/services/send_sms_service.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -210,125 +210,4 @@ String? policystatus,
|
|||||||
return null;
|
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:kmobile/api/services/branch_service.dart';
|
import 'package:kmobile/api/services/branch_service.dart';
|
||||||
import 'package:kmobile/api/services/cheque_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/limit_service.dart';
|
||||||
import 'package:kmobile/api/services/rtgs_service.dart';
|
import 'package:kmobile/api/services/rtgs_service.dart';
|
||||||
import 'package:kmobile/api/services/neft_service.dart';
|
import 'package:kmobile/api/services/neft_service.dart';
|
||||||
@@ -61,7 +60,6 @@ Future<void> setupDependencies() async {
|
|||||||
getIt.registerSingleton<BranchService>(BranchService(getIt<Dio>()));
|
getIt.registerSingleton<BranchService>(BranchService(getIt<Dio>()));
|
||||||
getIt.registerSingleton<ChequeService>(ChequeService(getIt<Dio>()));
|
getIt.registerSingleton<ChequeService>(ChequeService(getIt<Dio>()));
|
||||||
getIt.registerSingleton<YojnaService>(YojnaService(getIt<Dio>()));
|
getIt.registerSingleton<YojnaService>(YojnaService(getIt<Dio>()));
|
||||||
getIt.registerSingleton<DepositService>(DepositService(getIt<Dio>()));
|
|
||||||
getIt.registerLazySingleton<ChangePasswordService>(
|
getIt.registerLazySingleton<ChangePasswordService>(
|
||||||
() => ChangePasswordService(getIt<Dio>()),
|
() => ChangePasswordService(getIt<Dio>()),
|
||||||
);
|
);
|
||||||
@@ -80,7 +78,7 @@ Dio _createDioClient() {
|
|||||||
final dio = Dio(
|
final dio = Dio(
|
||||||
BaseOptions(
|
BaseOptions(
|
||||||
baseUrl:
|
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
|
//'http://lb-kccb-mobile-banking-app-848675342.ap-south-1.elb.amazonaws.com', //prod
|
||||||
//'https://kccbmbnk.net', //prod small
|
//'https://kccbmbnk.net', //prod small
|
||||||
connectTimeout: const Duration(seconds: 60),
|
connectTimeout: const Duration(seconds: 60),
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart'; // Keep if User model is generic
|
||||||
import 'package:kmobile/api/services/deposit_service.dart';
|
import 'package:kmobile/features/account_opening/screens/fd_screen.dart';
|
||||||
import 'package:kmobile/data/models/user.dart';
|
import 'package:kmobile/features/account_opening/screens/loan_screen.dart';
|
||||||
import 'package:kmobile/di/injection.dart';
|
import 'package:kmobile/features/account_opening/screens/rd_screen.dart';
|
||||||
import 'package:kmobile/l10n/app_localizations.dart';
|
import 'package:kmobile/features/account_opening/screens/td_screen.dart';
|
||||||
import 'package:kmobile/features/account_opening/screens/create_deposit_screen.dart';
|
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||||
|
import '../../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class AccountOpeningScreen extends StatefulWidget {
|
class AccountOpeningScreen extends StatefulWidget {
|
||||||
final List<User> users;
|
|
||||||
final int selectedIndex;
|
|
||||||
const AccountOpeningScreen({
|
const AccountOpeningScreen({
|
||||||
super.key,
|
super.key,
|
||||||
required this.users,
|
|
||||||
required this.selectedIndex,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -19,181 +16,160 @@ class AccountOpeningScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _AccountOpeningScreenState extends State<AccountOpeningScreen> {
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = AppLocalizations.of(context);
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(l10n.accountOpeningDeposit),
|
title: const Text(
|
||||||
|
"Account Opening",
|
||||||
|
),
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: Stack(
|
||||||
padding: const EdgeInsets.all(16.0),
|
children: [
|
||||||
child: Form(
|
Padding(
|
||||||
key: _formKey,
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
Card(
|
Expanded(
|
||||||
elevation: 2,
|
child: AccountOpeningCardTile(
|
||||||
child: Padding(
|
icon: Symbols.savings,
|
||||||
padding: const EdgeInsets.all(16.0),
|
label: "Fixed Deposit (FD)",
|
||||||
child: Text(
|
onTap: () {
|
||||||
l10n.accountOpeningDescription,
|
Navigator.push(
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
context,
|
||||||
),
|
MaterialPageRoute(
|
||||||
),
|
builder: (context) => const FdScreen(
|
||||||
),
|
),
|
||||||
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;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Expanded(
|
||||||
const SizedBox(height: 24),
|
child: AccountOpeningCardTile(
|
||||||
ElevatedButton(
|
icon: Symbols.currency_rupee,
|
||||||
onPressed: _isLoading
|
label: "Term Deposit",
|
||||||
? null
|
onTap: () {
|
||||||
: () async {
|
Navigator.push(
|
||||||
if (_formKey.currentState!.validate() &&
|
context,
|
||||||
_selectedAccount != null) {
|
MaterialPageRoute(
|
||||||
setState(() {
|
builder: (context) => const TermDepositScreen()
|
||||||
_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),
|
Expanded(
|
||||||
),
|
child: AccountOpeningCardTile(
|
||||||
|
icon: Symbols.account_balance,
|
||||||
|
label: AppLocalizations.of(context).recurringDeposit,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const RecurringDepositScreen() ),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: AccountOpeningCardTile(
|
||||||
|
icon: Symbols.credit_card,
|
||||||
|
label: "Request Loan",
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const LoanScreen()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IgnorePointer(
|
||||||
|
child: Center(
|
||||||
|
child: Opacity(
|
||||||
|
opacity: 0.07,
|
||||||
|
child: ClipOval(
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/logo.png',
|
||||||
|
width: 200,
|
||||||
|
height: 200,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccountOpeningCardTile extends StatelessWidget {
|
||||||
|
final IconData icon;
|
||||||
|
final String label;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
final bool disable;
|
||||||
|
|
||||||
|
const AccountOpeningCardTile({
|
||||||
|
super.key,
|
||||||
|
required this.icon,
|
||||||
|
required this.label,
|
||||||
|
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: 24.0, horizontal: 16.0),
|
||||||
|
child: Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
size: 48,
|
||||||
|
color: disable
|
||||||
|
? theme.disabledColor
|
||||||
|
: theme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: theme.textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: disable
|
||||||
|
? theme.disabledColor
|
||||||
|
: theme.colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
244
lib/features/account_opening/screens/fd_screen.dart
Normal file
244
lib/features/account_opening/screens/fd_screen.dart
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:kmobile/features/account_opening/screens/interest_rates_screen.dart';
|
||||||
|
|
||||||
|
class FdScreen extends StatefulWidget {
|
||||||
|
const FdScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FdScreen> createState() => _FdScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FdScreenState extends State<FdScreen> {
|
||||||
|
final TextEditingController _debitAccountController = TextEditingController();
|
||||||
|
final TextEditingController _amountController = TextEditingController();
|
||||||
|
final TextEditingController _yearsController = TextEditingController();
|
||||||
|
final TextEditingController _monthsController = TextEditingController();
|
||||||
|
final TextEditingController _daysController = TextEditingController();
|
||||||
|
|
||||||
|
String _rateOfInterest = "N/A";
|
||||||
|
String _maturityDate = "N/A";
|
||||||
|
String _maturityAmount = "N/A";
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_debitAccountController.dispose();
|
||||||
|
_amountController.dispose();
|
||||||
|
_yearsController.dispose();
|
||||||
|
_monthsController.dispose();
|
||||||
|
_daysController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Fixed Deposit (FD)'),
|
||||||
|
),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Explanation Tile
|
||||||
|
Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 20),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Earn more on your savings with a simple, secure Fixed Deposit.',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Debit Account Number
|
||||||
|
TextFormField(
|
||||||
|
controller: _debitAccountController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Debit Account Number',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Enter Amount
|
||||||
|
TextFormField(
|
||||||
|
controller: _amountController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Enter Amount',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
prefixText: '₹ '
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Duration and Interest Rates Link
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Duration',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const InterestRatesScreen()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'Interest Rates',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
|
// Duration TextBoxes
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _yearsController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Years',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _monthsController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Months',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _daysController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Days',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Rate of Interest and Maturity Date Display
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Rate of Interest',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Text(
|
||||||
|
_rateOfInterest,
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Maturity Date',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Text(
|
||||||
|
_maturityDate,
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
// Maturity Amount Display
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Maturity Amount',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Text(
|
||||||
|
_maturityAmount,
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
|
||||||
|
// Proceed Button
|
||||||
|
Center(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
// TODO: Implement proceed logic
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 15),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Proceed',
|
||||||
|
style: TextStyle(fontSize: 18),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class InterestRatesScreen extends StatelessWidget {
|
||||||
|
const InterestRatesScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Interest Rates'),
|
||||||
|
),
|
||||||
|
body: const Center(
|
||||||
|
child: Text('This page will display a table of interest rates.'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
lib/features/account_opening/screens/loan_screen.dart
Normal file
19
lib/features/account_opening/screens/loan_screen.dart
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class LoanScreen extends StatelessWidget {
|
||||||
|
const LoanScreen({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text("Request Loan"),
|
||||||
|
),
|
||||||
|
body: const Center(
|
||||||
|
child: Text("Loan Account"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
lib/features/account_opening/screens/rd_screen.dart
Normal file
19
lib/features/account_opening/screens/rd_screen.dart
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class RecurringDepositScreen extends StatelessWidget {
|
||||||
|
const RecurringDepositScreen({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text("Recurring Deposit"),
|
||||||
|
),
|
||||||
|
body: const Center(
|
||||||
|
child: Text("Recurring Deposit"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
lib/features/account_opening/screens/td_screen.dart
Normal file
19
lib/features/account_opening/screens/td_screen.dart
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class TermDepositScreen extends StatelessWidget {
|
||||||
|
const TermDepositScreen({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text("Term Deposit (TD)"),
|
||||||
|
),
|
||||||
|
body: const Center(
|
||||||
|
child: Text("Term Deposit (TD)"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import 'package:kmobile/app.dart';
|
|||||||
import 'package:kmobile/features/auth/screens/mpin_screen.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/set_password_screen.dart';
|
||||||
import 'package:kmobile/features/auth/screens/tnc_required_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 'package:kmobile/widgets/tnc_dialog.dart';
|
||||||
import '../../../l10n/app_localizations.dart';
|
import '../../../l10n/app_localizations.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -30,12 +31,23 @@ class LoginScreenState extends State<LoginScreen>
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _submitForm() {
|
void _submitForm() async {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
context.read<AuthCubit>().login(
|
final bool? verificationSuccess = await Navigator.of(context).push(
|
||||||
_customerNumberController.text.trim(),
|
MaterialPageRoute(
|
||||||
_passwordController.text,
|
builder: (_) => VerificationScreen(
|
||||||
);
|
customerNo: _customerNumberController.text.trim(),
|
||||||
|
password: _passwordController.text,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (verificationSuccess == true && mounted) {
|
||||||
|
context.read<AuthCubit>().login(
|
||||||
|
_customerNumberController.text.trim(),
|
||||||
|
_passwordController.text,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
177
lib/features/auth/screens/sms_verification_helper.dart
Normal file
177
lib/features/auth/screens/sms_verification_helper.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
170
lib/features/auth/screens/verification_screen.dart
Normal file
170
lib/features/auth/screens/verification_screen.dart
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
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(() {
|
||||||
|
if(e.toString().contains("NOT_VERIFIED")){
|
||||||
|
_statusMessage = "SIM details not found. Please ensure your mobile number is registered with the bank.";
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
_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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -187,8 +187,7 @@ class _BeneficiaryDetailsScreenState extends State<BeneficiaryDetailsScreen> {
|
|||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 24,
|
radius: 24,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
child:
|
child: getBankLogo(widget.beneficiary.bankName, context),
|
||||||
getBankLogo(widget.beneficiary.bankName, context),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Text(
|
Text(
|
||||||
@@ -275,3 +274,4 @@ class _BeneficiaryDetailsScreenState extends State<BeneficiaryDetailsScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ class _ManageBeneficiariesScreen extends State<ManageBeneficiariesScreen> {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText:
|
hintText:
|
||||||
AppLocalizations.of(context).searchByNameOrAccountHint,
|
AppLocalizations.of(context).searchByNameOrAccountHint,
|
||||||
|
|
||||||
prefixIcon: const Icon(Icons.search),
|
prefixIcon: const Icon(Icons.search),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
|||||||
@@ -93,8 +93,7 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
|
|||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ChequeManagementCardTile(
|
child: ChequeManagementCardTile(
|
||||||
icon: Symbols
|
icon: Symbols.check_circle, // Using check_circle for Positive Pay
|
||||||
.check_circle, // Using check_circle for Positive Pay
|
|
||||||
label: AppLocalizations.of(context).positivePayTitle,
|
label: AppLocalizations.of(context).positivePayTitle,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
@@ -192,4 +191,4 @@ class ChequeManagementCardTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ class _PositivePayScreenState extends State<PositivePayScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_filteredUsers = widget.users
|
_filteredUsers = widget.users
|
||||||
.where((user) => ['SA', 'SB', 'CA', 'CC'].contains(user.accountType))
|
.where((user) => ['SA', 'SB', 'CA', 'CC'].contains(user.accountType))
|
||||||
.toList();
|
.toList();
|
||||||
// Pre-fill the account number if possible
|
// Pre-fill the account number if possible
|
||||||
@@ -113,7 +113,7 @@ class _PositivePayScreenState extends State<PositivePayScreen> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showResponseDialog(String title, String message) async {
|
Future<void> _showResponseDialog(String title, String message) async {
|
||||||
return showDialog<void>(
|
return showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: false, // user must tap button!
|
barrierDismissible: false, // user must tap button!
|
||||||
@@ -145,8 +145,7 @@ class _PositivePayScreenState extends State<PositivePayScreen> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(
|
title: Text(
|
||||||
AppLocalizations.of(context)
|
AppLocalizations.of(context).positivePay, // Will be replaced by localization
|
||||||
.positivePay, // Will be replaced by localization
|
|
||||||
),
|
),
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
),
|
),
|
||||||
@@ -200,13 +199,12 @@ class _PositivePayScreenState extends State<PositivePayScreen> {
|
|||||||
final fromCheque = int.tryParse(_ciFromCheque ?? '');
|
final fromCheque = int.tryParse(_ciFromCheque ?? '');
|
||||||
final toCheque = int.tryParse(_ciToCheque ?? '');
|
final toCheque = int.tryParse(_ciToCheque ?? '');
|
||||||
if (chequeNumber == null) {
|
if (chequeNumber == null) {
|
||||||
return AppLocalizations.of(context)
|
return AppLocalizations.of(context).invalidChequeNumberFormatError;
|
||||||
.invalidChequeNumberFormatError;
|
|
||||||
}
|
}
|
||||||
if (fromCheque != null && toCheque != null) {
|
if (fromCheque != null && toCheque != null) {
|
||||||
if (chequeNumber < fromCheque || chequeNumber > toCheque) {
|
if (chequeNumber < fromCheque || chequeNumber > toCheque) {
|
||||||
return AppLocalizations.of(context)
|
return AppLocalizations.of(context).chequeNumberRangeError(
|
||||||
.chequeNumberRangeError(_ciFromCheque!, _ciToCheque!);
|
_ciFromCheque!, _ciToCheque!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -230,15 +228,13 @@ class _PositivePayScreenState extends State<PositivePayScreen> {
|
|||||||
);
|
);
|
||||||
if (pickedDate != null) {
|
if (pickedDate != null) {
|
||||||
// Format the date as you wish
|
// Format the date as you wish
|
||||||
String formattedDate =
|
String formattedDate = "${pickedDate.year}-${pickedDate.month.toString().padLeft(2, '0')}-${pickedDate.day.toString().padLeft(2, '0')}";
|
||||||
"${pickedDate.year}-${pickedDate.month.toString().padLeft(2, '0')}-${pickedDate.day.toString().padLeft(2, '0')}";
|
|
||||||
_chequeDateController.text = formattedDate;
|
_chequeDateController.text = formattedDate;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
return AppLocalizations.of(context)
|
return AppLocalizations.of(context).pleaseSelectChequeIssuedDate;
|
||||||
.pleaseSelectChequeIssuedDate;
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
@@ -259,8 +255,7 @@ class _PositivePayScreenState extends State<PositivePayScreen> {
|
|||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
prefixText: '₹ ',
|
prefixText: '₹ ',
|
||||||
),
|
),
|
||||||
keyboardType:
|
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||||
const TextInputType.numberWithOptions(decimal: true),
|
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
return AppLocalizations.of(context).pleaseEnterAmount;
|
return AppLocalizations.of(context).pleaseEnterAmount;
|
||||||
@@ -282,34 +277,34 @@ class _PositivePayScreenState extends State<PositivePayScreen> {
|
|||||||
try {
|
try {
|
||||||
final response = await _chequeService.registerPPS(
|
final response = await _chequeService.registerPPS(
|
||||||
cheque_no: _chequeNumberController.text,
|
cheque_no: _chequeNumberController.text,
|
||||||
account_number: _selectedAccount!.accountNo!,
|
account_number:
|
||||||
issue_date: _chequeDateController.text,
|
_selectedAccount!.accountNo!,
|
||||||
|
issue_date:
|
||||||
|
_chequeDateController.text,
|
||||||
amount: _amountController.text,
|
amount: _amountController.text,
|
||||||
payee_name: _payeeController.text,
|
payee_name: _payeeController.text,
|
||||||
tpin: pin,
|
tpin: pin,
|
||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
String responseString = response.toString();
|
String responseString = response.toString();
|
||||||
if (responseString ==
|
if(responseString == 'PPS Registered Successfully'){
|
||||||
'PPS Registered Successfully') {
|
|
||||||
_showResponseDialog('REGISTRATION SUCCESFUL',
|
_showResponseDialog('REGISTRATION SUCCESFUL',
|
||||||
'Your Positive Pay Request has been registered succesfully');
|
'Your Positive Pay Request has been registered succesfully');
|
||||||
}
|
}
|
||||||
if (responseString
|
if(responseString.contains('Cheque already registered')){
|
||||||
.contains('Cheque already registered')) {
|
|
||||||
_showResponseDialog('ERROR',
|
_showResponseDialog('ERROR',
|
||||||
'Your Request for the selected cheque number has already been registered');
|
'Your Request for the selected cheque number has already been registered');
|
||||||
}
|
}
|
||||||
if (responseString.contains('INCORRECT_TPIN')) {
|
if(responseString.contains('INCORRECT_TPIN')){
|
||||||
_showResponseDialog('Invalid TPIN',
|
_showResponseDialog('Invalid TPIN',
|
||||||
'The TPIN you entered is incorrect. Please try again.');
|
'The TPIN you entered is incorrect. Please try again.');
|
||||||
}
|
}
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
try {
|
try {
|
||||||
final errorBodyString =
|
final errorBodyString =
|
||||||
e.toString().split('Exception: ')[1];
|
e.toString().split('Exception: ')[1];
|
||||||
final errorBody = jsonDecode(errorBodyString);
|
final errorBody = jsonDecode(errorBodyString);
|
||||||
if (errorBody.containsKey('error') &&
|
if (errorBody.containsKey('error') &&
|
||||||
errorBody['error'] == 'INCORRECT_TPIN') {
|
errorBody['error'] == 'INCORRECT_TPIN') {
|
||||||
_showResponseDialog('Invalid TPIN',
|
_showResponseDialog('Invalid TPIN',
|
||||||
'The TPIN you entered is incorrect. Please try again.');
|
'The TPIN you entered is incorrect. Please try again.');
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ class RevokeStopMultipleChequesScreen extends StatefulWidget {
|
|||||||
_RevokeStopMultipleChequesScreenState();
|
_RevokeStopMultipleChequesScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RevokeStopMultipleChequesScreenState
|
class _RevokeStopMultipleChequesScreenState extends State<RevokeStopMultipleChequesScreen> {
|
||||||
extends State<RevokeStopMultipleChequesScreen> {
|
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
final _stopFromChequeNoController = TextEditingController();
|
final _stopFromChequeNoController = TextEditingController();
|
||||||
final _stopToChequeNoController = TextEditingController();
|
final _stopToChequeNoController = TextEditingController();
|
||||||
@@ -276,8 +275,7 @@ class _RevokeStopMultipleChequesScreenState
|
|||||||
removeToChequeNo:
|
removeToChequeNo:
|
||||||
_stopToChequeNoController.text,
|
_stopToChequeNoController.text,
|
||||||
removeIssueDate: _stopIssueDateController.text,
|
removeIssueDate: _stopIssueDateController.text,
|
||||||
removeExpiryDate:
|
removeExpiryDate: _stopExpiryDateController.text,
|
||||||
_stopExpiryDateController.text,
|
|
||||||
removeAmount: _stopAmountController.text,
|
removeAmount: _stopAmountController.text,
|
||||||
removeComment: _selectedComment == 'Other'
|
removeComment: _selectedComment == 'Other'
|
||||||
? _otherCommentController.text
|
? _otherCommentController.text
|
||||||
@@ -286,28 +284,24 @@ class _RevokeStopMultipleChequesScreenState
|
|||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final decodedResponse = jsonDecode(response);
|
final decodedResponse = jsonDecode(response);
|
||||||
String responseString = response
|
String responseString = response.toString(); // used as the case only for incorrect TPIN
|
||||||
.toString(); // used as the case only for incorrect TPIN
|
|
||||||
final status = decodedResponse['status'];
|
final status = decodedResponse['status'];
|
||||||
final message = decodedResponse['message'];
|
final message = decodedResponse['message'];
|
||||||
final code = decodedResponse['code'];
|
final code = decodedResponse['code'];
|
||||||
if (status == 'SUCCESS') {
|
if (status == 'SUCCESS') {
|
||||||
_showResponseDialog('Success', message);
|
_showResponseDialog('Success', message);
|
||||||
}
|
} if (status == 'ERROR') {
|
||||||
if (status == 'ERROR') {
|
|
||||||
String errMessage = "error";
|
String errMessage = "error";
|
||||||
if (code == '0172') {
|
if(code == '0172') {
|
||||||
errMessage =
|
errMessage = 'The selected Cheque is not stopped';
|
||||||
'The selected Cheque is not stopped';
|
} else if(code == '0748') {
|
||||||
} else if (code == '0748') {
|
errMessage = 'The selected Cheque is already presented';
|
||||||
errMessage =
|
|
||||||
'The selected Cheque is already presented';
|
|
||||||
}
|
}
|
||||||
_showResponseDialog('Error', errMessage);
|
_showResponseDialog('Error', errMessage);
|
||||||
}
|
}
|
||||||
if (responseString.contains('INCORRECT_TPIN')) {
|
if(responseString.contains('INCORRECT_TPIN')){
|
||||||
_showResponseDialog('Invalid TPIN',
|
_showResponseDialog('Invalid TPIN',
|
||||||
'The TPIN you entered is incorrect. Please try again.');
|
'The TPIN you entered is incorrect. Please try again.');
|
||||||
}
|
}
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ class RevokeStopChequeScreen extends StatefulWidget {
|
|||||||
final List<User> users;
|
final List<User> users;
|
||||||
final int selectedIndex;
|
final int selectedIndex;
|
||||||
|
|
||||||
const RevokeStopChequeScreen({
|
const RevokeStopChequeScreen(
|
||||||
super.key,
|
{
|
||||||
required this.users,
|
super.key,
|
||||||
required this.selectedIndex,
|
required this.users,
|
||||||
});
|
required this.selectedIndex,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<RevokeStopChequeScreen> createState() => _RevokeStopChequeScreenState();
|
State<RevokeStopChequeScreen> createState() => _RevokeStopChequeScreenState();
|
||||||
@@ -198,10 +199,8 @@ class _RevokeStopChequeScreenState extends State<RevokeStopChequeScreen> {
|
|||||||
selectedAccount: _selectedAccount!,
|
selectedAccount: _selectedAccount!,
|
||||||
date: _stCheques.first.Date!,
|
date: _stCheques.first.Date!,
|
||||||
instrType: _stCheques.first.InstrType!,
|
instrType: _stCheques.first.InstrType!,
|
||||||
fromCheque: _ciFromCheque ??
|
fromCheque: _ciFromCheque ?? _stCheques.first.fromCheque!,
|
||||||
_stCheques.first.fromCheque!,
|
toCheque: _ciToCheque ?? _stCheques.first.toCheque!,
|
||||||
toCheque:
|
|
||||||
_ciToCheque ?? _stCheques.first.toCheque!,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -217,8 +216,7 @@ class _RevokeStopChequeScreenState extends State<RevokeStopChequeScreen> {
|
|||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
AppLocalizations.of(context)
|
AppLocalizations.of(context).revokeSingleStopTitle,
|
||||||
.revokeSingleStopTitle,
|
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
@@ -250,10 +248,8 @@ class _RevokeStopChequeScreenState extends State<RevokeStopChequeScreen> {
|
|||||||
selectedAccount: _selectedAccount!,
|
selectedAccount: _selectedAccount!,
|
||||||
date: _stCheques.first.Date!,
|
date: _stCheques.first.Date!,
|
||||||
instrType: _stCheques.first.InstrType!,
|
instrType: _stCheques.first.InstrType!,
|
||||||
fromCheque: _ciFromCheque ??
|
fromCheque: _ciFromCheque ?? _stCheques.first.fromCheque!,
|
||||||
_stCheques.first.fromCheque!,
|
toCheque: _ciToCheque ?? _stCheques.first.toCheque!,
|
||||||
toCheque:
|
|
||||||
_ciToCheque ?? _stCheques.first.toCheque!,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -270,7 +266,7 @@ class _RevokeStopChequeScreenState extends State<RevokeStopChequeScreen> {
|
|||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
AppLocalizations.of(context).revokeMultipleStops,
|
AppLocalizations.of(context).revokeMultipleStops,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
|||||||
@@ -23,12 +23,10 @@ class RevokeStopSingleChequeScreen extends StatefulWidget {
|
|||||||
required this.toCheque});
|
required this.toCheque});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<RevokeStopSingleChequeScreen> createState() =>
|
State<RevokeStopSingleChequeScreen> createState() => _RevokeStopSingleChequeScreenState();
|
||||||
_RevokeStopSingleChequeScreenState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RevokeStopSingleChequeScreenState
|
class _RevokeStopSingleChequeScreenState extends State<RevokeStopSingleChequeScreen> {
|
||||||
extends State<RevokeStopSingleChequeScreen> {
|
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
final _stopFromChequeNoController = TextEditingController();
|
final _stopFromChequeNoController = TextEditingController();
|
||||||
final _stopIssueDateController = TextEditingController();
|
final _stopIssueDateController = TextEditingController();
|
||||||
@@ -91,7 +89,7 @@ class _RevokeStopSingleChequeScreenState
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(AppLocalizations.of(context).revokeSingleStopTitle)),
|
title: Text(AppLocalizations.of(context).revokeSingleStopTitle)),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Form(
|
child: Form(
|
||||||
@@ -243,8 +241,7 @@ class _RevokeStopSingleChequeScreenState
|
|||||||
removeToChequeNo:
|
removeToChequeNo:
|
||||||
_stopFromChequeNoController.text,
|
_stopFromChequeNoController.text,
|
||||||
removeIssueDate: _stopIssueDateController.text,
|
removeIssueDate: _stopIssueDateController.text,
|
||||||
removeExpiryDate:
|
removeExpiryDate: _stopExpiryDateController.text,
|
||||||
_stopExpiryDateController.text,
|
|
||||||
removeAmount: _stopAmountController.text,
|
removeAmount: _stopAmountController.text,
|
||||||
removeComment: _selectedComment == 'Other'
|
removeComment: _selectedComment == 'Other'
|
||||||
? _otherCommentController.text
|
? _otherCommentController.text
|
||||||
@@ -253,35 +250,31 @@ class _RevokeStopSingleChequeScreenState
|
|||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final decodedResponse = jsonDecode(response);
|
final decodedResponse = jsonDecode(response);
|
||||||
String responseString = response
|
String responseString = response.toString(); // used as the case only for incorrect TPIN
|
||||||
.toString(); // used as the case only for incorrect TPIN
|
|
||||||
final status = decodedResponse['status'];
|
final status = decodedResponse['status'];
|
||||||
final message = decodedResponse['message'];
|
final message = decodedResponse['message'];
|
||||||
final code = decodedResponse['code'];
|
final code = decodedResponse['code'];
|
||||||
if (status == 'SUCCESS') {
|
if (status == 'SUCCESS') {
|
||||||
_showResponseDialog('Success', message);
|
_showResponseDialog('Success', message);
|
||||||
}
|
} if (status == 'ERROR') {
|
||||||
if (status == 'ERROR') {
|
|
||||||
String errMessage = "error";
|
String errMessage = "error";
|
||||||
if (code == '0172') {
|
if(code == '0172') {
|
||||||
errMessage =
|
errMessage = 'The selected Cheque is not stopped';
|
||||||
'The selected Cheque is not stopped';
|
} else if(code == '0748') {
|
||||||
} else if (code == '0748') {
|
errMessage = 'The selected Cheque is already presented';
|
||||||
errMessage =
|
|
||||||
'The selected Cheque is already presented';
|
|
||||||
}
|
}
|
||||||
_showResponseDialog('Error', errMessage);
|
_showResponseDialog('Error', errMessage);
|
||||||
}
|
}
|
||||||
if (responseString.contains('INCORRECT_TPIN')) {
|
if(responseString.contains('INCORRECT_TPIN')){
|
||||||
_showResponseDialog('Invalid TPIN',
|
_showResponseDialog('Invalid TPIN',
|
||||||
'The TPIN you entered is incorrect. Please try again.');
|
'The TPIN you entered is incorrect. Please try again.');
|
||||||
}
|
}
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
try {
|
try {
|
||||||
final errorBodyString =
|
final errorBodyString =
|
||||||
e.toString().split('Exception: ')[1];
|
e.toString().split('Exception: ')[1];
|
||||||
final errorBody = jsonDecode(errorBodyString);
|
final errorBody = jsonDecode(errorBodyString);
|
||||||
if (errorBody.containsKey('error') &&
|
if (errorBody.containsKey('error') &&
|
||||||
errorBody['error'] == 'INCORRECT_TPIN') {
|
errorBody['error'] == 'INCORRECT_TPIN') {
|
||||||
_showResponseDialog('Invalid TPIN',
|
_showResponseDialog('Invalid TPIN',
|
||||||
'The TPIN you entered is incorrect. Please try again.');
|
'The TPIN you entered is incorrect. Please try again.');
|
||||||
|
|||||||
@@ -309,28 +309,24 @@ class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
|
|||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final decodedResponse = jsonDecode(response);
|
final decodedResponse = jsonDecode(response);
|
||||||
String responseString = response
|
String responseString = response.toString(); // used as the case only for incorrect TPIN
|
||||||
.toString(); // used as the case only for incorrect TPIN
|
|
||||||
final status = decodedResponse['status'];
|
final status = decodedResponse['status'];
|
||||||
final message = decodedResponse['message'];
|
final message = decodedResponse['message'];
|
||||||
final code = decodedResponse['code'];
|
final code = decodedResponse['code'];
|
||||||
if (status == 'SUCCESS') {
|
if (status == 'SUCCESS') {
|
||||||
_showResponseDialog('Success', message);
|
_showResponseDialog('Success', message);
|
||||||
}
|
} if (status == 'ERROR') {
|
||||||
if (status == 'ERROR') {
|
|
||||||
String errMessage = "error";
|
String errMessage = "error";
|
||||||
if (code == '0429') {
|
if(code == '0429') {
|
||||||
errMessage =
|
errMessage = 'The selected Cheque is already stopped';
|
||||||
'The selected Cheque is already stopped';
|
} else if(code == '0748') {
|
||||||
} else if (code == '0748') {
|
errMessage = 'The selected Cheque is already presented';
|
||||||
errMessage =
|
|
||||||
'The selected Cheque is already presented';
|
|
||||||
}
|
}
|
||||||
_showResponseDialog('Error', errMessage);
|
_showResponseDialog('Error', errMessage);
|
||||||
}
|
}
|
||||||
if (responseString.contains('INCORRECT_TPIN')) {
|
if(responseString.contains('INCORRECT_TPIN')){
|
||||||
_showResponseDialog('Invalid TPIN',
|
_showResponseDialog('Invalid TPIN',
|
||||||
'The TPIN you entered is incorrect. Please try again.');
|
'The TPIN you entered is incorrect. Please try again.');
|
||||||
}
|
}
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -278,35 +278,31 @@ class _StopSingleChequeScreenState extends State<StopSingleChequeScreen> {
|
|||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final decodedResponse = jsonDecode(response);
|
final decodedResponse = jsonDecode(response);
|
||||||
String responseString = response
|
String responseString = response.toString(); // used as the case only for incorrect TPIN
|
||||||
.toString(); // used as the case only for incorrect TPIN
|
|
||||||
final status = decodedResponse['status'];
|
final status = decodedResponse['status'];
|
||||||
final message = decodedResponse['message'];
|
final message = decodedResponse['message'];
|
||||||
final code = decodedResponse['code'];
|
final code = decodedResponse['code'];
|
||||||
if (status == 'SUCCESS') {
|
if (status == 'SUCCESS') {
|
||||||
_showResponseDialog('Success', message);
|
_showResponseDialog('Success', message);
|
||||||
}
|
} if (status == 'ERROR') {
|
||||||
if (status == 'ERROR') {
|
|
||||||
String errMessage = "error";
|
String errMessage = "error";
|
||||||
if (code == '0429') {
|
if(code == '0429') {
|
||||||
errMessage =
|
errMessage = 'The selected Cheque is already stopped';
|
||||||
'The selected Cheque is already stopped';
|
} else if(code == '0748') {
|
||||||
} else if (code == '0748') {
|
errMessage = 'The selected Cheque is already presented';
|
||||||
errMessage =
|
|
||||||
'The selected Cheque is already presented';
|
|
||||||
}
|
}
|
||||||
_showResponseDialog('Error', errMessage);
|
_showResponseDialog('Error', errMessage);
|
||||||
}
|
}
|
||||||
if (responseString.contains('INCORRECT_TPIN')) {
|
if(responseString.contains('INCORRECT_TPIN')){
|
||||||
_showResponseDialog('Invalid TPIN',
|
_showResponseDialog('Invalid TPIN',
|
||||||
'The TPIN you entered is incorrect. Please try again.');
|
'The TPIN you entered is incorrect. Please try again.');
|
||||||
}
|
}
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
try {
|
try {
|
||||||
final errorBodyString =
|
final errorBodyString =
|
||||||
e.toString().split('Exception: ')[1];
|
e.toString().split('Exception: ')[1];
|
||||||
final errorBody = jsonDecode(errorBodyString);
|
final errorBody = jsonDecode(errorBodyString);
|
||||||
if (errorBody.containsKey('error') &&
|
if (errorBody.containsKey('error') &&
|
||||||
errorBody['error'] == 'INCORRECT_TPIN') {
|
errorBody['error'] == 'INCORRECT_TPIN') {
|
||||||
_showResponseDialog('Invalid TPIN',
|
_showResponseDialog('Invalid TPIN',
|
||||||
'The TPIN you entered is incorrect. Please try again.');
|
'The TPIN you entered is incorrect. Please try again.');
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import 'package:kmobile/features/beneficiaries/screens/manage_beneficiaries_scre
|
|||||||
import 'package:kmobile/features/fund_transfer/screens/fund_transfer_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/profile/profile_screen.dart';
|
||||||
import 'package:kmobile/features/quick_pay/screens/quick_pay_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/service/screens/branch_locator_screen.dart';
|
||||||
import 'package:kmobile/features/yojna/screens/gov_scheme_screen.dart';
|
import 'package:kmobile/features/yojna/screens/gov_scheme_screen.dart';
|
||||||
import 'package:kmobile/security/secure_storage.dart';
|
import 'package:kmobile/security/secure_storage.dart';
|
||||||
import 'package:local_auth/local_auth.dart';
|
import 'package:local_auth/local_auth.dart';
|
||||||
@@ -618,15 +618,14 @@ class _DashboardScreenState extends State<DashboardScreen>
|
|||||||
selectedIndex: selectedAccountIndex,
|
selectedIndex: selectedAccountIndex,
|
||||||
)));
|
)));
|
||||||
}),
|
}),
|
||||||
_buildQuickLink(Symbols.box, AppLocalizations.of(context).createnewdeposit, () {
|
_buildQuickLink(Icons.location_pin,
|
||||||
|
AppLocalizations.of(context).branchlocator, () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => AccountOpeningScreen(
|
builder: (context) =>
|
||||||
users: users,
|
const BranchLocatorScreen()));
|
||||||
selectedIndex: selectedAccountIndex,
|
}, disable: false),
|
||||||
)));
|
|
||||||
}, disable: isPaymentDisabled),
|
|
||||||
_buildQuickLink(Icons.group,
|
_buildQuickLink(Icons.group,
|
||||||
AppLocalizations.of(context).manageBeneficiary,
|
AppLocalizations.of(context).manageBeneficiary,
|
||||||
() {
|
() {
|
||||||
@@ -637,20 +636,15 @@ class _DashboardScreenState extends State<DashboardScreen>
|
|||||||
ManageBeneficiariesScreen(
|
ManageBeneficiariesScreen(
|
||||||
customerName: currAccount.name!)));
|
customerName: currAccount.name!)));
|
||||||
}, disable: false),
|
}, disable: false),
|
||||||
_buildQuickLink(
|
_buildQuickLink(Symbols.family_group,
|
||||||
Symbols.family_group,
|
AppLocalizations.of(context).governmentSchemes, () {
|
||||||
AppLocalizations.of(context).governmentSchemes,
|
Navigator.push(
|
||||||
() {
|
context,
|
||||||
Navigator.push(
|
MaterialPageRoute(
|
||||||
context,
|
builder: (context) =>
|
||||||
MaterialPageRoute(
|
GovSchemeScreen(users: users,
|
||||||
builder: (context) => GovSchemeScreen(
|
selectedIndex: selectedAccountIndex)));
|
||||||
users: users,
|
}),
|
||||||
selectedIndex: selectedAccountIndex,
|
|
||||||
)));
|
|
||||||
},
|
|
||||||
disable: isPaymentDisabled,
|
|
||||||
),
|
|
||||||
_buildQuickLink(
|
_buildQuickLink(
|
||||||
Symbols.checkbook,
|
Symbols.checkbook,
|
||||||
AppLocalizations.of(context).chequeManagement,
|
AppLocalizations.of(context).chequeManagement,
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class SecuritySettingsScreen extends StatelessWidget {
|
|||||||
margin: const EdgeInsets.only(bottom: 10),
|
margin: const EdgeInsets.only(bottom: 10),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: const Icon(Icons.password),
|
leading: const Icon(Icons.password),
|
||||||
title: Text(AppLocalizations.of(context).changeTpin),
|
title: const Text('Change TPIN'),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final authService = getIt<AuthService>();
|
final authService = getIt<AuthService>();
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import 'package:kmobile/features/account_opening/screens/account_opening_screen.dart';
|
import 'package:kmobile/features/account_opening/screens/account_opening_screen.dart';
|
||||||
|
import 'package:kmobile/features/card/screens/card_management_screen.dart';
|
||||||
import 'package:kmobile/features/service/screens/atm_locator_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/enquiry_screen.dart';
|
||||||
import 'package:kmobile/features/service/screens/upi_screen.dart';
|
|
||||||
import '../../../l10n/app_localizations.dart';
|
import '../../../l10n/app_localizations.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||||
@@ -60,23 +59,9 @@ class _ServiceScreen extends State<ServiceScreen> {
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ServiceManagementTile(
|
child: ServiceManagementTile(
|
||||||
icon: Icons.location_pin,
|
icon: Symbols.location_pin,
|
||||||
label: AppLocalizations.of(context).branchlocator,
|
|
||||||
onTap: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const BranchLocatorScreen()));
|
|
||||||
},
|
|
||||||
disabled: false,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: ServiceManagementTile(
|
|
||||||
icon: Symbols.payment,
|
|
||||||
label: AppLocalizations.of(context).atmlocator,
|
label: AppLocalizations.of(context).atmlocator,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
@@ -89,18 +74,30 @@ class _ServiceScreen extends State<ServiceScreen> {
|
|||||||
),
|
),
|
||||||
// Expanded(
|
// Expanded(
|
||||||
// child: ServiceManagementTile(
|
// child: ServiceManagementTile(
|
||||||
// icon: Symbols.upi_pay,
|
// icon: Symbols.box,
|
||||||
// label: "Receive Money by UPI",
|
// label: "Account Opening",
|
||||||
// onTap: () {
|
// onTap: () {
|
||||||
// Navigator.push(
|
// Navigator.push(
|
||||||
// context,
|
// context,
|
||||||
// MaterialPageRoute(
|
// MaterialPageRoute(
|
||||||
// builder: (context) => const UpiScreen()));
|
// builder: (context) => const AccountOpeningScreen()));
|
||||||
|
// },
|
||||||
|
// disabled: false,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// Expanded(
|
||||||
|
// child: ServiceManagementTile(
|
||||||
|
// icon: Symbols.credit_card,
|
||||||
|
// label: AppLocalizations.of(context).cardManagement,
|
||||||
|
// onTap: () {
|
||||||
|
// Navigator.push(
|
||||||
|
// context,
|
||||||
|
// MaterialPageRoute(
|
||||||
|
// builder: (context) => const CardManagementScreen()));
|
||||||
// },
|
// },
|
||||||
// disabled: false,
|
// disabled: false,
|
||||||
// ),
|
// ),
|
||||||
// ),
|
// ),
|
||||||
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ServiceManagementTile(
|
child: ServiceManagementTile(
|
||||||
icon: Symbols.support_agent,
|
icon: Symbols.support_agent,
|
||||||
@@ -113,7 +110,8 @@ class _ServiceScreen extends State<ServiceScreen> {
|
|||||||
},
|
},
|
||||||
disabled: false,
|
disabled: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// No Spacer() needed here as Expanded children will fill space
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -164,34 +162,33 @@ class ServiceManagementTile extends StatelessWidget {
|
|||||||
onTap:
|
onTap:
|
||||||
disabled ? null : onTap, // Disable InkWell if the tile is disabled
|
disabled ? null : onTap, // Disable InkWell if the tile is disabled
|
||||||
borderRadius: BorderRadius.circular(12.0),
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
icon,
|
icon,
|
||||||
size: 48, // Make icon larger
|
size: 48, // Make icon larger
|
||||||
color: disabled
|
color:
|
||||||
? theme.disabledColor
|
disabled ? theme.disabledColor : theme.colorScheme.primary,
|
||||||
: 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.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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:kmobile/api/services/yojna_service.dart';
|
|
||||||
import 'package:kmobile/data/models/user.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';
|
import 'package:kmobile/l10n/app_localizations.dart';
|
||||||
|
|
||||||
class APYScreen extends StatefulWidget {
|
class APYScreen extends StatefulWidget {
|
||||||
@@ -21,8 +18,6 @@ class APYScreen extends StatefulWidget {
|
|||||||
class _APYScreenState extends State<APYScreen> {
|
class _APYScreenState extends State<APYScreen> {
|
||||||
User? _selectedAccount;
|
User? _selectedAccount;
|
||||||
List<User> _filteredUsers = [];
|
List<User> _filteredUsers = [];
|
||||||
final _formKey = GlobalKey<FormState>();
|
|
||||||
bool _isLoading = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -30,7 +25,7 @@ class _APYScreenState extends State<APYScreen> {
|
|||||||
_filteredUsers = widget.users
|
_filteredUsers = widget.users
|
||||||
.where((user) => ['SA', 'SB', 'CA', 'CC'].contains(user.accountType))
|
.where((user) => ['SA', 'SB', 'CA', 'CC'].contains(user.accountType))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
// Pre-fill the account number if possible
|
// Pre-fill the account number if possible
|
||||||
if (widget.users.isNotEmpty && widget.selectedIndex < widget.users.length) {
|
if (widget.users.isNotEmpty && widget.selectedIndex < widget.users.length) {
|
||||||
if (_filteredUsers.isNotEmpty) {
|
if (_filteredUsers.isNotEmpty) {
|
||||||
@@ -59,136 +54,60 @@ class _APYScreenState extends State<APYScreen> {
|
|||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Form(
|
child: Column(
|
||||||
key: _formKey,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
child: Column(
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
Card(
|
||||||
children: [
|
elevation: 2,
|
||||||
Card(
|
child: Padding(
|
||||||
elevation: 2,
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Padding(
|
child: Text(
|
||||||
padding: const EdgeInsets.all(16.0),
|
l10n.apyDescription,
|
||||||
child: Text(
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
l10n.apyDescription,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
Card(
|
const SizedBox(height: 16),
|
||||||
elevation: 2,
|
Card(
|
||||||
child: Padding(
|
elevation: 2,
|
||||||
padding: const EdgeInsets.all(16.0),
|
child: Padding(
|
||||||
child: Column(
|
padding: const EdgeInsets.all(16.0),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
DropdownButtonFormField<User>(
|
children: [
|
||||||
value: _selectedAccount,
|
DropdownButtonFormField<User>(
|
||||||
decoration: InputDecoration(
|
value: _selectedAccount,
|
||||||
labelText: l10n.accountNumber,
|
decoration: InputDecoration(
|
||||||
border: const OutlineInputBorder(),
|
labelText: l10n.accountNumber,
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
border: const OutlineInputBorder(),
|
||||||
vertical: 20, horizontal: 12),
|
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;
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
items: _filteredUsers.map((user) {
|
||||||
),
|
return DropdownMenuItem<User>(
|
||||||
),
|
value: user,
|
||||||
),
|
child: Text(user.accountNo.toString()),
|
||||||
const SizedBox(height: 24),
|
);
|
||||||
ElevatedButton(
|
}).toList(),
|
||||||
onPressed: _isLoading
|
onChanged: (User? newUser) {
|
||||||
? null
|
setState(() {
|
||||||
: () async {
|
_selectedAccount = newUser;
|
||||||
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(
|
validator: (value) {
|
||||||
backgroundColor:
|
if (value == null) {
|
||||||
Theme.of(context).colorScheme.primaryContainer,
|
return l10n.accountNumberRequired;
|
||||||
foregroundColor:
|
}
|
||||||
Theme.of(context).colorScheme.onPrimaryContainer,
|
return null;
|
||||||
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),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,24 +50,24 @@ class _GovSchemeScreenState extends State<GovSchemeScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
// Expanded(
|
||||||
child: GovSchemeTile(
|
// child: GovSchemeTile(
|
||||||
logoText: "APY",
|
// logoText: "APY",
|
||||||
label: l10n.registerForAtalPensionYojana,
|
// label: l10n.registerForAtalPensionYojana,
|
||||||
subtitle: l10n.secureYourFutureAPY,
|
// subtitle: l10n.secureYourFutureAPY,
|
||||||
onTap: () {
|
// onTap: () {
|
||||||
Navigator.push(
|
// Navigator.push(
|
||||||
context,
|
// context,
|
||||||
MaterialPageRoute(
|
// MaterialPageRoute(
|
||||||
builder: (context) => APYScreen(
|
// builder: (context) => APYScreen(
|
||||||
users: widget.users,
|
// users: widget.users,
|
||||||
selectedIndex: widget.selectedIndex,
|
// selectedIndex: widget.selectedIndex,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
); // Action for APY will be added later
|
// );// Action for APY will be added later
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ class _PMMainScreenState extends State<PMMainScreen> {
|
|||||||
String? _selectedScheme;
|
String? _selectedScheme;
|
||||||
|
|
||||||
List<String> _getSchemes(AppLocalizations l10n) => [
|
List<String> _getSchemes(AppLocalizations l10n) => [
|
||||||
l10n.pmjjbyFull,
|
l10n.pmjjbyFull,
|
||||||
l10n.pmsbyFull,
|
l10n.pmsbyFull,
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -76,8 +76,7 @@ class _PMMainScreenState extends State<PMMainScreen> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String schemeCode =
|
final String schemeCode = (_selectedScheme == l10n.pmjjbyFull) ? '02' : '01';
|
||||||
(_selectedScheme == l10n.pmjjbyFull) ? '02' : '01';
|
|
||||||
|
|
||||||
// Show loading
|
// Show loading
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
@@ -104,9 +103,7 @@ class _PMMainScreenState extends State<PMMainScreen> {
|
|||||||
Map<String, dynamic>? data;
|
Map<String, dynamic>? data;
|
||||||
if (response is Map<String, dynamic>) {
|
if (response is Map<String, dynamic>) {
|
||||||
data = response;
|
data = response;
|
||||||
} else if (response is List &&
|
} else if (response is List && response.isNotEmpty && response[0] is Map<String, dynamic>) {
|
||||||
response.isNotEmpty &&
|
|
||||||
response[0] is Map<String, dynamic>) {
|
|
||||||
data = response[0] as Map<String, dynamic>;
|
data = response[0] as Map<String, dynamic>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,15 +169,15 @@ class _PMMainScreenState extends State<PMMainScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = AppLocalizations.of(context);
|
final l10n = AppLocalizations.of(context);
|
||||||
final schemes = _getSchemes(l10n);
|
final schemes = _getSchemes(l10n);
|
||||||
|
|
||||||
// Ensure _selectedScheme is valid if it was set in a different language
|
// Ensure _selectedScheme is valid if it was set in a different language
|
||||||
if (_selectedScheme != null && !schemes.contains(_selectedScheme)) {
|
if (_selectedScheme != null && !schemes.contains(_selectedScheme)) {
|
||||||
// Try to find the corresponding scheme in the new language
|
// Try to find the corresponding scheme in the new language
|
||||||
// This is a bit tricky, but since we only have two:
|
// This is a bit tricky, but since we only have two:
|
||||||
// If it doesn't match, it might be from the other language.
|
// If it doesn't match, it might be from the other language.
|
||||||
// For simplicity, we can just reset it or try to guess.
|
// 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
|
// Better to use non-localized values for the state, but let's just reset for now if it doesn't match
|
||||||
_selectedScheme = null;
|
_selectedScheme = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -246,23 +243,23 @@ class _PMMainScreenState extends State<PMMainScreen> {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: l10n.selectScheme,
|
labelText: l10n.selectScheme,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding:
|
||||||
vertical: 12, horizontal: 12),
|
const EdgeInsets.symmetric(vertical: 12, horizontal: 12),
|
||||||
),
|
),
|
||||||
selectedItemBuilder: (BuildContext context) {
|
selectedItemBuilder: (BuildContext context) {
|
||||||
return schemes.map((String scheme) {
|
return schemes.map((String scheme) {
|
||||||
return Container(
|
return Container(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Text(
|
child: Text(
|
||||||
scheme,
|
scheme,
|
||||||
style: const TextStyle(fontSize: 14),
|
style: const TextStyle(fontSize: 14),
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.visible,
|
overflow: TextOverflow.visible,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
},
|
},
|
||||||
items: schemes.map((String scheme) {
|
items: schemes.map((String scheme) {
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
value: scheme,
|
value: scheme,
|
||||||
@@ -271,6 +268,7 @@ class _PMMainScreenState extends State<PMMainScreen> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
scheme,
|
scheme,
|
||||||
style: const TextStyle(fontSize: 15),
|
style: const TextStyle(fontSize: 15),
|
||||||
|
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -338,3 +336,4 @@ class _PMMainScreenState extends State<PMMainScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -119,8 +119,7 @@ class _PMJJBYEnquiryScreenState extends State<PMJJBYEnquiryScreen> {
|
|||||||
labelText: l10n.selectFinancialYear,
|
labelText: l10n.selectFinancialYear,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
prefixIcon: const Icon(Icons.calendar_today),
|
prefixIcon: const Icon(Icons.calendar_today),
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
horizontal: 12, vertical: 8),
|
|
||||||
),
|
),
|
||||||
items: _financialYears.map((String year) {
|
items: _financialYears.map((String year) {
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
@@ -154,9 +153,7 @@ class _PMJJBYEnquiryScreenState extends State<PMJJBYEnquiryScreen> {
|
|||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
_errorMessage!,
|
_errorMessage!,
|
||||||
style: TextStyle(
|
style: TextStyle(color: Colors.red.shade700, fontWeight: FontWeight.bold),
|
||||||
color: Colors.red.shade700,
|
|
||||||
fontWeight: FontWeight.bold),
|
|
||||||
//textAlign: Center,
|
//textAlign: Center,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -180,20 +177,13 @@ class _PMJJBYEnquiryScreenState extends State<PMJJBYEnquiryScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
_buildDetailRow(
|
_buildDetailRow(l10n.customerName, _enquiryData!['customername']),
|
||||||
l10n.customerName, _enquiryData!['customername']),
|
_buildDetailRow(l10n.policyNumber, _enquiryData!['policynumber']),
|
||||||
_buildDetailRow(
|
_buildDetailRow(l10n.accountNumber, _enquiryData!['accountno']),
|
||||||
l10n.policyNumber, _enquiryData!['policynumber']),
|
_buildDetailRow(l10n.premiumAmount, _enquiryData!['preimiumamount']),
|
||||||
_buildDetailRow(
|
_buildDetailRow(l10n.nomineeName, _enquiryData!['nomineename']),
|
||||||
l10n.accountNumber, _enquiryData!['accountno']),
|
_buildDetailRow(l10n.date, _enquiryData!['transactiondate']),
|
||||||
_buildDetailRow(
|
_buildDetailRow(l10n.journalNo, _enquiryData!['journalno']),
|
||||||
l10n.premiumAmount, _enquiryData!['preimiumamount']),
|
|
||||||
_buildDetailRow(
|
|
||||||
l10n.nomineeName, _enquiryData!['nomineename']),
|
|
||||||
_buildDetailRow(
|
|
||||||
l10n.date, _enquiryData!['transactiondate']),
|
|
||||||
_buildDetailRow(
|
|
||||||
l10n.journalNo, _enquiryData!['journalno']),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -212,8 +202,7 @@ class _PMJJBYEnquiryScreenState extends State<PMJJBYEnquiryScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
label,
|
label,
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontWeight: FontWeight.w500, color: Colors.grey),
|
||||||
fontWeight: FontWeight.w500, color: Colors.grey),
|
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
@@ -15,45 +15,26 @@ class _PMJJBYScreenState extends State<PMJJBYScreen> {
|
|||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
// Controllers for all requested fields
|
// Controllers for all requested fields
|
||||||
late final _aadhaarController =
|
late final _aadhaarController = TextEditingController(text: widget.initialData?['aadharno']?.toString());
|
||||||
TextEditingController(text: widget.initialData?['aadharno']?.toString());
|
late final _accountNoController = TextEditingController(text: widget.initialData?['accountno']?.toString());
|
||||||
late final _accountNoController =
|
late final _balanceController = TextEditingController(text: widget.initialData?['availablebalance']?.toString());
|
||||||
TextEditingController(text: widget.initialData?['accountno']?.toString());
|
late final _countryController = TextEditingController(text: widget.initialData?['country']?.toString() ?? 'IN');
|
||||||
late final _balanceController = TextEditingController(
|
late final _dobController = TextEditingController(text: widget.initialData?['customerdob']?.toString());
|
||||||
text: widget.initialData?['availablebalance']?.toString());
|
late final _nameController = TextEditingController(text: widget.initialData?['customername']?.toString());
|
||||||
late final _countryController = TextEditingController(
|
late final _customerNoController = TextEditingController(text: widget.initialData?['customerno']?.toString());
|
||||||
text: widget.initialData?['country']?.toString() ?? 'IN');
|
late final _acctOpeningDateController = TextEditingController(text: widget.initialData?['dateofacctopening']?.toString());
|
||||||
late final _dobController = TextEditingController(
|
late final _emailController = TextEditingController(text: widget.initialData?['emailid']?.toString());
|
||||||
text: widget.initialData?['customerdob']?.toString());
|
late final _financialYearController = TextEditingController(text: widget.initialData?['financialyear']?.toString());
|
||||||
late final _nameController = TextEditingController(
|
late final _genderController = TextEditingController(text: widget.initialData?['gender']?.toString());
|
||||||
text: widget.initialData?['customername']?.toString());
|
late final _ifscController = TextEditingController(text: widget.initialData?['ifsccode']?.toString());
|
||||||
late final _customerNoController = TextEditingController(
|
late final _marriedController = TextEditingController(text: widget.initialData?['married']?.toString());
|
||||||
text: widget.initialData?['customerno']?.toString());
|
late final _mobileController = TextEditingController(text: widget.initialData?['mobileno']?.toString());
|
||||||
late final _acctOpeningDateController = TextEditingController(
|
late final _panController = TextEditingController(text: widget.initialData?['pan']?.toString());
|
||||||
text: widget.initialData?['dateofacctopening']?.toString());
|
late final _pincodeController = TextEditingController(text: widget.initialData?['pincode']?.toString());
|
||||||
late final _emailController =
|
late final _policyNumberController = TextEditingController(text: widget.initialData?['policynumber']?.toString());
|
||||||
TextEditingController(text: widget.initialData?['emailid']?.toString());
|
late final _premiumAmountController = TextEditingController(text: widget.initialData?['premiumamount']?.toString());
|
||||||
late final _financialYearController = TextEditingController(
|
late final _stateController = TextEditingController(text: widget.initialData?['state']?.toString());
|
||||||
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
|
// Mapping options
|
||||||
final Map<String, String> _healthStatusOptions = {
|
final Map<String, String> _healthStatusOptions = {
|
||||||
'1': 'Excellent',
|
'1': 'Excellent',
|
||||||
@@ -109,20 +90,16 @@ class _PMJJBYScreenState extends State<PMJJBYScreen> {
|
|||||||
// Initialize dropdown controllers if data exists in initialData
|
// Initialize dropdown controllers if data exists in initialData
|
||||||
if (widget.initialData != null) {
|
if (widget.initialData != null) {
|
||||||
if (widget.initialData!.containsKey('ruralcategory')) {
|
if (widget.initialData!.containsKey('ruralcategory')) {
|
||||||
_ruralCategoryController.text =
|
_ruralCategoryController.text = widget.initialData!['ruralcategory'].toString();
|
||||||
widget.initialData!['ruralcategory'].toString();
|
|
||||||
}
|
}
|
||||||
if (widget.initialData!.containsKey('healthstatus')) {
|
if (widget.initialData!.containsKey('healthstatus')) {
|
||||||
_healthStatusController.text =
|
_healthStatusController.text = widget.initialData!['healthstatus'].toString();
|
||||||
widget.initialData!['healthstatus'].toString();
|
|
||||||
}
|
}
|
||||||
if (widget.initialData!.containsKey('nomineerelationship')) {
|
if (widget.initialData!.containsKey('nomineerelationship')) {
|
||||||
_nomineeRelationshipController.text =
|
_nomineeRelationshipController.text = widget.initialData!['nomineerelationship'].toString();
|
||||||
widget.initialData!['nomineerelationship'].toString();
|
|
||||||
}
|
}
|
||||||
if (widget.initialData!.containsKey('nomineeminor')) {
|
if (widget.initialData!.containsKey('nomineeminor')) {
|
||||||
_nomineeMinorController.text =
|
_nomineeMinorController.text = widget.initialData!['nomineeminor'].toString();
|
||||||
widget.initialData!['nomineeminor'].toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,9 +136,9 @@ class _PMJJBYScreenState extends State<PMJJBYScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _isFetched(String key) {
|
bool _isFetched(String key) {
|
||||||
return widget.initialData != null &&
|
return widget.initialData != null &&
|
||||||
widget.initialData!.containsKey(key) &&
|
widget.initialData!.containsKey(key) &&
|
||||||
widget.initialData![key]?.toString().isNotEmpty == true;
|
widget.initialData![key]?.toString().isNotEmpty == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleRegister() async {
|
Future<void> _handleRegister() async {
|
||||||
@@ -203,8 +180,8 @@ class _PMJJBYScreenState extends State<PMJJBYScreen> {
|
|||||||
ruralcategory: _ruralCategoryController.text,
|
ruralcategory: _ruralCategoryController.text,
|
||||||
);
|
);
|
||||||
String x = response.toString();
|
String x = response.toString();
|
||||||
if (x.contains('RECORD ALREADY EXISTS')) {
|
if(x.contains('RECORD ALREADY EXISTS')){
|
||||||
x = l10n.recordAlreadyExists;
|
x= l10n.recordAlreadyExists;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@@ -252,77 +229,38 @@ class _PMJJBYScreenState extends State<PMJJBYScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
_buildTextField(_nameController, l10n.customerName,
|
_buildTextField(_nameController, l10n.customerName, readOnly: _isFetched('customername')),
|
||||||
readOnly: _isFetched('customername')),
|
_buildTextField(_customerNoController, l10n.customerNo, readOnly: _isFetched('customerno')),
|
||||||
_buildTextField(_customerNoController, l10n.customerNo,
|
_buildTextField(_accountNoController, l10n.accountNumber, keyboardType: TextInputType.number, readOnly: _isFetched('accountno')),
|
||||||
readOnly: _isFetched('customerno')),
|
_buildTextField(_balanceController, l10n.availableBalance, keyboardType: TextInputType.number, readOnly: _isFetched('availablebalance')),
|
||||||
_buildTextField(_accountNoController, l10n.accountNumber,
|
_buildTextField(_aadhaarController, l10n.aadhaarNo, keyboardType: TextInputType.number, readOnly: _isFetched('aadharno')),
|
||||||
keyboardType: TextInputType.number,
|
_buildTextField(_dobController, l10n.customerDobFormat, readOnly: _isFetched('customerdob')),
|
||||||
readOnly: _isFetched('accountno')),
|
_buildTextField(_genderController, l10n.gender, readOnly: _isFetched('gender')),
|
||||||
_buildTextField(_balanceController, l10n.availableBalance,
|
_buildTextField(_marriedController, l10n.marriedYesNo, readOnly: _isFetched('married')),
|
||||||
keyboardType: TextInputType.number,
|
_buildTextField(_mobileController, l10n.mobileNumber, keyboardType: TextInputType.phone, readOnly: _isFetched('mobileno')),
|
||||||
readOnly: _isFetched('availablebalance')),
|
_buildTextField(_emailController, 'Email ID', keyboardType: TextInputType.emailAddress, readOnly: _isFetched('emailid')),
|
||||||
_buildTextField(_aadhaarController, l10n.aadhaarNo,
|
_buildTextField(_panController, l10n.pan, readOnly: _isFetched('pan')),
|
||||||
keyboardType: TextInputType.number,
|
_buildTextField(_ifscController, l10n.ifscCode, readOnly: _isFetched('ifsccode')),
|
||||||
readOnly: _isFetched('aadharno')),
|
_buildTextField(_acctOpeningDateController, l10n.dateOfAcctOpening, readOnly: _isFetched('dateofacctopening')),
|
||||||
_buildTextField(_dobController, l10n.customerDobFormat,
|
_buildTextField(_pincodeController, l10n.pincode, keyboardType: TextInputType.number, readOnly: _isFetched('pincode')),
|
||||||
readOnly: _isFetched('customerdob')),
|
_buildTextField(_stateController, l10n.state, readOnly: _isFetched('state')),
|
||||||
_buildTextField(_genderController, l10n.gender,
|
_buildTextField(_countryController, l10n.country, readOnly: _isFetched('country')),
|
||||||
readOnly: _isFetched('gender')),
|
_buildDropdownField(_ruralCategoryController, l10n.ruralCategory, _ruralOptions, readOnly: _isFetched('ruralcategory')),
|
||||||
_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),
|
const Divider(height: 32),
|
||||||
Text(l10n.policyDetails,
|
Text(l10n.policyDetails, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 18, fontWeight: FontWeight.bold)),
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildTextField(_policyNumberController, l10n.policyNumber,
|
_buildTextField(_policyNumberController, l10n.policyNumber, readOnly: _isFetched('policynumber')),
|
||||||
readOnly: _isFetched('policynumber')),
|
_buildTextField(_premiumAmountController, l10n.premiumAmount, keyboardType: TextInputType.number, readOnly: _isFetched('premiumamount')),
|
||||||
_buildTextField(_premiumAmountController, l10n.premiumAmount,
|
_buildTextField(_financialYearController, l10n.financialYear, readOnly: _isFetched('financialyear')),
|
||||||
keyboardType: TextInputType.number,
|
_buildDropdownField(_healthStatusController, l10n.healthStatus, _healthStatusOptions),
|
||||||
readOnly: _isFetched('premiumamount')),
|
_buildTextField(_collectionChannelController, l10n.collectionChannel),
|
||||||
_buildTextField(_financialYearController, l10n.financialYear,
|
|
||||||
readOnly: _isFetched('financialyear')),
|
|
||||||
_buildDropdownField(_healthStatusController, l10n.healthStatus,
|
|
||||||
_healthStatusOptions),
|
|
||||||
_buildTextField(
|
|
||||||
_collectionChannelController, l10n.collectionChannel),
|
|
||||||
const Divider(height: 32),
|
const Divider(height: 32),
|
||||||
Text(l10n.nomineeDetails,
|
Text(l10n.nomineeDetails, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 18, fontWeight: FontWeight.bold)),
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildTextField(_nomineeNameController, l10n.nomineeName),
|
_buildTextField(_nomineeNameController, l10n.nomineeName),
|
||||||
_buildTextField(_nomineeAddressController, l10n.nomineeAddress),
|
_buildTextField(_nomineeAddressController, l10n.nomineeAddress),
|
||||||
_buildDropdownField(_nomineeRelationshipController,
|
_buildDropdownField(_nomineeRelationshipController, l10n.nomineeRelationship, _relationshipOptions, readOnly: _isFetched('nomineerelationship')),
|
||||||
l10n.nomineeRelationship, _relationshipOptions,
|
_buildDropdownField(_nomineeMinorController, l10n.nomineeMinor, _minorOptions, readOnly: _isFetched('nomineeminor')),
|
||||||
readOnly: _isFetched('nomineerelationship')),
|
|
||||||
_buildDropdownField(
|
|
||||||
_nomineeMinorController, l10n.nomineeMinor, _minorOptions,
|
|
||||||
readOnly: _isFetched('nomineeminor')),
|
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: _handleRegister,
|
onPressed: _handleRegister,
|
||||||
@@ -336,8 +274,7 @@ class _PMJJBYScreenState extends State<PMJJBYScreen> {
|
|||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
l10n.register,
|
l10n.register,
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||||
fontSize: 16, fontWeight: FontWeight.bold),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
@@ -348,29 +285,25 @@ class _PMJJBYScreenState extends State<PMJJBYScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDropdownField(TextEditingController controller, String label,
|
Widget _buildDropdownField(
|
||||||
Map<String, String> options,
|
TextEditingController controller, String label, Map<String, String> options,
|
||||||
{bool readOnly = false}) {
|
{bool readOnly = false}) {
|
||||||
// Determine current value
|
// Determine current value
|
||||||
String? currentValue =
|
String? currentValue = options.containsKey(controller.text) ? controller.text : null;
|
||||||
options.containsKey(controller.text) ? controller.text : null;
|
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 16.0),
|
padding: const EdgeInsets.only(bottom: 16.0),
|
||||||
child: DropdownButtonFormField<String>(
|
child: DropdownButtonFormField<String>(
|
||||||
value: currentValue,
|
value: currentValue,
|
||||||
onChanged: readOnly
|
onChanged: readOnly ? null : (newValue) {
|
||||||
? null
|
setState(() {
|
||||||
: (newValue) {
|
controller.text = newValue ?? '';
|
||||||
setState(() {
|
});
|
||||||
controller.text = newValue ?? '';
|
},
|
||||||
});
|
|
||||||
},
|
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: label,
|
labelText: label,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
contentPadding:
|
contentPadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
|
||||||
const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
|
|
||||||
),
|
),
|
||||||
items: options.entries.map((entry) {
|
items: options.entries.map((entry) {
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
@@ -382,9 +315,7 @@ class _PMJJBYScreenState extends State<PMJJBYScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTextField(TextEditingController controller, String label,
|
Widget _buildTextField(TextEditingController controller, String label, {TextInputType keyboardType = TextInputType.text, bool readOnly = false}) {
|
||||||
{TextInputType keyboardType = TextInputType.text,
|
|
||||||
bool readOnly = false}) {
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 16.0),
|
padding: const EdgeInsets.only(bottom: 16.0),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
@@ -393,8 +324,7 @@ class _PMJJBYScreenState extends State<PMJJBYScreen> {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: label,
|
labelText: label,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
contentPadding:
|
contentPadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
|
||||||
const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
|
|
||||||
),
|
),
|
||||||
keyboardType: keyboardType,
|
keyboardType: keyboardType,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -119,8 +119,7 @@ class _PMSBYEnquiryScreenState extends State<PMSBYEnquiryScreen> {
|
|||||||
labelText: l10n.selectFinancialYear,
|
labelText: l10n.selectFinancialYear,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
prefixIcon: const Icon(Icons.calendar_today),
|
prefixIcon: const Icon(Icons.calendar_today),
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
horizontal: 12, vertical: 8),
|
|
||||||
),
|
),
|
||||||
items: _financialYears.map((String year) {
|
items: _financialYears.map((String year) {
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
@@ -154,9 +153,7 @@ class _PMSBYEnquiryScreenState extends State<PMSBYEnquiryScreen> {
|
|||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
_errorMessage!,
|
_errorMessage!,
|
||||||
style: TextStyle(
|
style: TextStyle(color: Colors.red.shade700, fontWeight: FontWeight.bold),
|
||||||
color: Colors.red.shade700,
|
|
||||||
fontWeight: FontWeight.bold),
|
|
||||||
//textAlign: Center,
|
//textAlign: Center,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -180,24 +177,13 @@ class _PMSBYEnquiryScreenState extends State<PMSBYEnquiryScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
_buildDetailRow(
|
_buildDetailRow(l10n.customerName, _enquiryData!['customername']),
|
||||||
l10n.customerName, _enquiryData!['customername']),
|
_buildDetailRow(l10n.policyNumber, _enquiryData!['policynumber'] ?? _enquiryData!['policyno']),
|
||||||
_buildDetailRow(
|
_buildDetailRow(l10n.accountNumber, _enquiryData!['accountno']),
|
||||||
l10n.policyNumber,
|
_buildDetailRow(l10n.premiumAmount, _enquiryData!['preimiumamount'] ?? _enquiryData!['premiumamount']),
|
||||||
_enquiryData!['policynumber'] ??
|
_buildDetailRow(l10n.nomineeName, _enquiryData!['nomineename']),
|
||||||
_enquiryData!['policyno']),
|
_buildDetailRow(l10n.date, _enquiryData!['transactiondate']),
|
||||||
_buildDetailRow(
|
_buildDetailRow(l10n.journalNo, _enquiryData!['journalno']),
|
||||||
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']),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -216,8 +202,7 @@ class _PMSBYEnquiryScreenState extends State<PMSBYEnquiryScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
label,
|
label,
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontWeight: FontWeight.w500, color: Colors.grey),
|
||||||
fontWeight: FontWeight.w500, color: Colors.grey),
|
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
@@ -15,54 +15,30 @@ class _PMSBYScreenState extends State<PMSBYScreen> {
|
|||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
// Controllers for all requested fields
|
// Controllers for all requested fields
|
||||||
late final _aadhaarController =
|
late final _aadhaarController = TextEditingController(text: widget.initialData?['aadharno']?.toString());
|
||||||
TextEditingController(text: widget.initialData?['aadharno']?.toString());
|
late final _accountNoController = TextEditingController(text: widget.initialData?['accountno']?.toString());
|
||||||
late final _accountNoController =
|
late final _balanceController = TextEditingController(text: widget.initialData?['availablebalance']?.toString());
|
||||||
TextEditingController(text: widget.initialData?['accountno']?.toString());
|
late final _countryController = TextEditingController(text: widget.initialData?['country']?.toString() ?? 'IN');
|
||||||
late final _balanceController = TextEditingController(
|
late final _dobController = TextEditingController(text: widget.initialData?['customerdob']?.toString());
|
||||||
text: widget.initialData?['availablebalance']?.toString());
|
late final _nameController = TextEditingController(text: widget.initialData?['customername']?.toString());
|
||||||
late final _countryController = TextEditingController(
|
late final _customerNoController = TextEditingController(text: widget.initialData?['customerno']?.toString());
|
||||||
text: widget.initialData?['country']?.toString() ?? 'IN');
|
late final _acctOpeningDateController = TextEditingController(text: widget.initialData?['dateofacctopening']?.toString());
|
||||||
late final _dobController = TextEditingController(
|
late final _emailController = TextEditingController(text: widget.initialData?['emailid']?.toString());
|
||||||
text: widget.initialData?['customerdob']?.toString());
|
late final _financialYearController = TextEditingController(text: widget.initialData?['financialyear']?.toString());
|
||||||
late final _nameController = TextEditingController(
|
late final _genderController = TextEditingController(text: widget.initialData?['gender']?.toString());
|
||||||
text: widget.initialData?['customername']?.toString());
|
late final _ifscController = TextEditingController(text: widget.initialData?['ifsccode']?.toString());
|
||||||
late final _customerNoController = TextEditingController(
|
late final _marriedController = TextEditingController(text: widget.initialData?['married']?.toString());
|
||||||
text: widget.initialData?['customerno']?.toString());
|
late final _mobileController = TextEditingController(text: widget.initialData?['mobileno']?.toString());
|
||||||
late final _acctOpeningDateController = TextEditingController(
|
late final _panController = TextEditingController(text: widget.initialData?['pan']?.toString());
|
||||||
text: widget.initialData?['dateofacctopening']?.toString());
|
late final _pincodeController = TextEditingController(text: widget.initialData?['pincode']?.toString());
|
||||||
late final _emailController =
|
late final _policyNumberController = TextEditingController(text: widget.initialData?['policyno']?.toString());
|
||||||
TextEditingController(text: widget.initialData?['emailid']?.toString());
|
late final _premiumAmountController = TextEditingController(text: widget.initialData?['premiumamount']?.toString());
|
||||||
late final _financialYearController = TextEditingController(
|
late final _stateController = TextEditingController(text: widget.initialData?['state']?.toString());
|
||||||
text: widget.initialData?['financialyear']?.toString());
|
late final _address1Controller = TextEditingController(text: widget.initialData?['address1']?.toString());
|
||||||
late final _genderController =
|
late final _address2Controller = TextEditingController(text: widget.initialData?['address2']?.toString());
|
||||||
TextEditingController(text: widget.initialData?['gender']?.toString());
|
late final _cityController = TextEditingController(text: widget.initialData?['city']?.toString());
|
||||||
late final _ifscController =
|
late final _relationWithNomineeController = TextEditingController(text: widget.initialData?['relationwithnominee']?.toString());
|
||||||
TextEditingController(text: widget.initialData?['ifsccode']?.toString());
|
late final _policyStatusController = TextEditingController(text: widget.initialData?['policystatus']?.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
|
// Mapping options
|
||||||
final Map<String, String> _healthStatusOptions = {
|
final Map<String, String> _healthStatusOptions = {
|
||||||
@@ -119,20 +95,16 @@ class _PMSBYScreenState extends State<PMSBYScreen> {
|
|||||||
// Initialize dropdown controllers if data exists in initialData
|
// Initialize dropdown controllers if data exists in initialData
|
||||||
if (widget.initialData != null) {
|
if (widget.initialData != null) {
|
||||||
if (widget.initialData!.containsKey('ruralcategory')) {
|
if (widget.initialData!.containsKey('ruralcategory')) {
|
||||||
_ruralCategoryController.text =
|
_ruralCategoryController.text = widget.initialData!['ruralcategory'].toString();
|
||||||
widget.initialData!['ruralcategory'].toString();
|
|
||||||
}
|
}
|
||||||
if (widget.initialData!.containsKey('healthstatus')) {
|
if (widget.initialData!.containsKey('healthstatus')) {
|
||||||
_healthStatusController.text =
|
_healthStatusController.text = widget.initialData!['healthstatus'].toString();
|
||||||
widget.initialData!['healthstatus'].toString();
|
|
||||||
}
|
}
|
||||||
if (widget.initialData!.containsKey('relationwithnominee')) {
|
if (widget.initialData!.containsKey('relationwithnominee')) {
|
||||||
_nomineeRelationshipController.text =
|
_nomineeRelationshipController.text = widget.initialData!['relationwithnominee'].toString();
|
||||||
widget.initialData!['relationwithnominee'].toString();
|
|
||||||
}
|
}
|
||||||
if (widget.initialData!.containsKey('nomineeminor')) {
|
if (widget.initialData!.containsKey('nomineeminor')) {
|
||||||
_nomineeMinorController.text =
|
_nomineeMinorController.text = widget.initialData!['nomineeminor'].toString();
|
||||||
widget.initialData!['nomineeminor'].toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,9 +146,9 @@ class _PMSBYScreenState extends State<PMSBYScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _isFetched(String key) {
|
bool _isFetched(String key) {
|
||||||
return widget.initialData != null &&
|
return widget.initialData != null &&
|
||||||
widget.initialData!.containsKey(key) &&
|
widget.initialData!.containsKey(key) &&
|
||||||
widget.initialData![key]?.toString().isNotEmpty == true;
|
widget.initialData![key]?.toString().isNotEmpty == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleRegister() async {
|
Future<void> _handleRegister() async {
|
||||||
@@ -220,8 +192,8 @@ class _PMSBYScreenState extends State<PMSBYScreen> {
|
|||||||
policystatus: _policyStatusController.text,
|
policystatus: _policyStatusController.text,
|
||||||
);
|
);
|
||||||
String x = response.toString();
|
String x = response.toString();
|
||||||
if (x.contains('RECORD ALREADY EXISTS')) {
|
if(x.contains('RECORD ALREADY EXISTS')){
|
||||||
x = l10n.recordAlreadyExists;
|
x= l10n.recordAlreadyExists;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@@ -269,87 +241,43 @@ class _PMSBYScreenState extends State<PMSBYScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
_buildTextField(_nameController, l10n.customerName,
|
_buildTextField(_nameController, l10n.customerName, readOnly: _isFetched('customername')),
|
||||||
readOnly: _isFetched('customername')),
|
_buildTextField(_customerNoController, l10n.customerNo, readOnly: _isFetched('customerno')),
|
||||||
_buildTextField(_customerNoController, l10n.customerNo,
|
_buildTextField(_accountNoController, l10n.accountNumber, keyboardType: TextInputType.number, readOnly: _isFetched('accountno')),
|
||||||
readOnly: _isFetched('customerno')),
|
_buildTextField(_balanceController, l10n.availableBalance, keyboardType: TextInputType.number, readOnly: _isFetched('availablebalance')),
|
||||||
_buildTextField(_accountNoController, l10n.accountNumber,
|
_buildTextField(_aadhaarController, l10n.aadhaarNo, keyboardType: TextInputType.number, readOnly: _isFetched('aadharno')),
|
||||||
keyboardType: TextInputType.number,
|
_buildTextField(_dobController, l10n.customerDobFormat, readOnly: _isFetched('customerdob')),
|
||||||
readOnly: _isFetched('accountno')),
|
_buildTextField(_genderController, l10n.gender, readOnly: _isFetched('gender')),
|
||||||
_buildTextField(_balanceController, l10n.availableBalance,
|
_buildTextField(_marriedController, l10n.marriedYesNo, readOnly: _isFetched('married')),
|
||||||
keyboardType: TextInputType.number,
|
_buildTextField(_mobileController, l10n.mobileNumber, keyboardType: TextInputType.phone, readOnly: _isFetched('mobileno')),
|
||||||
readOnly: _isFetched('availablebalance')),
|
_buildTextField(_emailController, 'Email ID', keyboardType: TextInputType.emailAddress, readOnly: _isFetched('emailid')),
|
||||||
_buildTextField(_aadhaarController, l10n.aadhaarNo,
|
_buildTextField(_address1Controller, l10n.address1, readOnly: _isFetched('address1')),
|
||||||
keyboardType: TextInputType.number,
|
_buildTextField(_address2Controller, l10n.address2, readOnly: _isFetched('address2')),
|
||||||
readOnly: _isFetched('aadharno')),
|
_buildTextField(_cityController, l10n.city, readOnly: _isFetched('city')),
|
||||||
_buildTextField(_dobController, l10n.customerDobFormat,
|
_buildTextField(_panController, l10n.pan, readOnly: _isFetched('pan')),
|
||||||
readOnly: _isFetched('customerdob')),
|
_buildTextField(_ifscController, l10n.ifscCode, readOnly: _isFetched('ifsccode')),
|
||||||
_buildTextField(_genderController, l10n.gender,
|
_buildTextField(_acctOpeningDateController, l10n.dateOfAcctOpening, readOnly: _isFetched('dateofacctopening')),
|
||||||
readOnly: _isFetched('gender')),
|
_buildTextField(_pincodeController, l10n.pincode, keyboardType: TextInputType.number, readOnly: _isFetched('pincode')),
|
||||||
_buildTextField(_marriedController, l10n.marriedYesNo,
|
_buildTextField(_stateController, l10n.state, readOnly: _isFetched('state')),
|
||||||
readOnly: _isFetched('married')),
|
_buildTextField(_countryController, l10n.country, readOnly: _isFetched('country')),
|
||||||
_buildTextField(_mobileController, l10n.mobileNumber,
|
_buildDropdownField(_ruralCategoryController, l10n.ruralCategory, _ruralOptions, readOnly: _isFetched('ruralcategory')),
|
||||||
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),
|
const Divider(height: 32),
|
||||||
Text(l10n.policyDetails,
|
Text(l10n.policyDetails, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 18, fontWeight: FontWeight.bold)),
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildTextField(_policyNumberController, l10n.policyNumber,
|
_buildTextField(_policyNumberController, l10n.policyNumber, readOnly: _isFetched('policyno')),
|
||||||
readOnly: _isFetched('policyno')),
|
_buildTextField(_premiumAmountController, l10n.premiumAmount, keyboardType: TextInputType.number, readOnly: _isFetched('premiumamount')),
|
||||||
_buildTextField(_premiumAmountController, l10n.premiumAmount,
|
_buildTextField(_financialYearController, l10n.financialYear, readOnly: _isFetched('financialyear')),
|
||||||
keyboardType: TextInputType.number,
|
_buildTextField(_policyStatusController, l10n.policyStatus, readOnly: _isFetched('policystatus')),
|
||||||
readOnly: _isFetched('premiumamount')),
|
_buildDropdownField(_healthStatusController, l10n.healthStatus, _healthStatusOptions),
|
||||||
_buildTextField(_financialYearController, l10n.financialYear,
|
_buildTextField(_collectionChannelController, l10n.collectionChannel),
|
||||||
readOnly: _isFetched('financialyear')),
|
|
||||||
_buildTextField(_policyStatusController, l10n.policyStatus,
|
|
||||||
readOnly: _isFetched('policystatus')),
|
|
||||||
_buildDropdownField(_healthStatusController, l10n.healthStatus,
|
|
||||||
_healthStatusOptions),
|
|
||||||
_buildTextField(
|
|
||||||
_collectionChannelController, l10n.collectionChannel),
|
|
||||||
const Divider(height: 32),
|
const Divider(height: 32),
|
||||||
Text(l10n.nomineeDetails,
|
Text(l10n.nomineeDetails, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 18, fontWeight: FontWeight.bold)),
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildTextField(_nomineeNameController, l10n.nomineeName),
|
_buildTextField(_nomineeNameController, l10n.nomineeName),
|
||||||
_buildTextField(_nomineeAddressController, l10n.nomineeAddress),
|
_buildTextField(_nomineeAddressController, l10n.nomineeAddress),
|
||||||
_buildDropdownField(_relationWithNomineeController,
|
_buildDropdownField(_relationWithNomineeController, l10n.relationWithNominee, _relationshipOptions, readOnly: _isFetched('relationwithnominee')),
|
||||||
l10n.relationWithNominee, _relationshipOptions,
|
_buildTextField(_nomineeRelationshipController, l10n.nomineeRelationship),
|
||||||
readOnly: _isFetched('relationwithnominee')),
|
_buildDropdownField(_nomineeMinorController, l10n.nomineeMinor, _minorOptions, readOnly: _isFetched('nomineeminor')),
|
||||||
_buildTextField(
|
|
||||||
_nomineeRelationshipController, l10n.nomineeRelationship),
|
|
||||||
_buildDropdownField(
|
|
||||||
_nomineeMinorController, l10n.nomineeMinor, _minorOptions,
|
|
||||||
readOnly: _isFetched('nomineeminor')),
|
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: _handleRegister,
|
onPressed: _handleRegister,
|
||||||
@@ -363,8 +291,7 @@ class _PMSBYScreenState extends State<PMSBYScreen> {
|
|||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
l10n.register,
|
l10n.register,
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||||
fontSize: 16, fontWeight: FontWeight.bold),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
@@ -375,29 +302,25 @@ class _PMSBYScreenState extends State<PMSBYScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDropdownField(TextEditingController controller, String label,
|
Widget _buildDropdownField(
|
||||||
Map<String, String> options,
|
TextEditingController controller, String label, Map<String, String> options,
|
||||||
{bool readOnly = false}) {
|
{bool readOnly = false}) {
|
||||||
// Determine current value
|
// Determine current value
|
||||||
String? currentValue =
|
String? currentValue = options.containsKey(controller.text) ? controller.text : null;
|
||||||
options.containsKey(controller.text) ? controller.text : null;
|
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 16.0),
|
padding: const EdgeInsets.only(bottom: 16.0),
|
||||||
child: DropdownButtonFormField<String>(
|
child: DropdownButtonFormField<String>(
|
||||||
value: currentValue,
|
value: currentValue,
|
||||||
onChanged: readOnly
|
onChanged: readOnly ? null : (newValue) {
|
||||||
? null
|
setState(() {
|
||||||
: (newValue) {
|
controller.text = newValue ?? '';
|
||||||
setState(() {
|
});
|
||||||
controller.text = newValue ?? '';
|
},
|
||||||
});
|
|
||||||
},
|
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: label,
|
labelText: label,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
contentPadding:
|
contentPadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
|
||||||
const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
|
|
||||||
),
|
),
|
||||||
items: options.entries.map((entry) {
|
items: options.entries.map((entry) {
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
@@ -409,9 +332,7 @@ class _PMSBYScreenState extends State<PMSBYScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTextField(TextEditingController controller, String label,
|
Widget _buildTextField(TextEditingController controller, String label, {TextInputType keyboardType = TextInputType.text, bool readOnly = false}) {
|
||||||
{TextInputType keyboardType = TextInputType.text,
|
|
||||||
bool readOnly = false}) {
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 16.0),
|
padding: const EdgeInsets.only(bottom: 16.0),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
@@ -420,8 +341,7 @@ class _PMSBYScreenState extends State<PMSBYScreen> {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: label,
|
labelText: label,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
contentPadding:
|
contentPadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
|
||||||
const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
|
|
||||||
),
|
),
|
||||||
keyboardType: keyboardType,
|
keyboardType: keyboardType,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
"accountStatement": "Account \n Statement",
|
"accountStatement": "Account \n Statement",
|
||||||
"handleCheque": "Handle \n Cheque",
|
"handleCheque": "Handle \n Cheque",
|
||||||
"manageBeneficiary": "Manage \n Beneficiary",
|
"manageBeneficiary": "Manage \n Beneficiary",
|
||||||
"contactUs": "Contact Us",
|
"contactUs": "Contact \n Us",
|
||||||
"addBeneficiary": "Add Beneficiary",
|
"addBeneficiary": "Add Beneficiary",
|
||||||
"confirmAccountNumber": "Confirm Account Number",
|
"confirmAccountNumber": "Confirm Account Number",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
@@ -641,7 +641,6 @@
|
|||||||
"collectionChannel": "Collection Channel",
|
"collectionChannel": "Collection Channel",
|
||||||
"nomineeDetails": "Nominee Details",
|
"nomineeDetails": "Nominee Details",
|
||||||
"nomineeAddress": "Nominee Address",
|
"nomineeAddress": "Nominee Address",
|
||||||
"relationWithNominee": "Relation with Nominee",
|
|
||||||
"nomineeRelationship": "Nominee Relationship",
|
"nomineeRelationship": "Nominee Relationship",
|
||||||
"nomineeMinor": "Nominee Minor",
|
"nomineeMinor": "Nominee Minor",
|
||||||
"response": "Response",
|
"response": "Response",
|
||||||
@@ -649,123 +648,7 @@
|
|||||||
"address1": "Address 1",
|
"address1": "Address 1",
|
||||||
"address2": "Address 2",
|
"address2": "Address 2",
|
||||||
"city": "City",
|
"city": "City",
|
||||||
"relationwithnominee": "Relation with Nominee",
|
"relationWithNominee": "Relation with Nominee",
|
||||||
"policyStatus": "Policy Status",
|
"policyStatus": "Policy Status",
|
||||||
"emailId": "Email ID",
|
"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": "46–90 Days",
|
|
||||||
"days91_180Option": "91–180 Days",
|
|
||||||
"days180_1YearOption": "180 Days–1 Year",
|
|
||||||
"year1_18MonthsOption": "1 Year–<18 Months",
|
|
||||||
"months18_2YearsOption": "18 Months–<2 Years",
|
|
||||||
"years2_3Option": "2–3 Years",
|
|
||||||
"years3_10Option": "3–10 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"
|
|
||||||
}
|
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
"accountStatement": "खाता \n विवरण",
|
"accountStatement": "खाता \n विवरण",
|
||||||
"handleCheque": "चेक \n संभालें",
|
"handleCheque": "चेक \n संभालें",
|
||||||
"manageBeneficiary": "लाभार्थी \n प्रबंधन",
|
"manageBeneficiary": "लाभार्थी \n प्रबंधन",
|
||||||
"contactUs": "संपर्क करें",
|
"contactUs": "संपर्क \n करें",
|
||||||
"addBeneficiary": "लाभार्थी जोड़ें",
|
"addBeneficiary": "लाभार्थी जोड़ें",
|
||||||
"confirmAccountNumber": "खाता संख्या की पुष्टि करें",
|
"confirmAccountNumber": "खाता संख्या की पुष्टि करें",
|
||||||
"name": "नाम",
|
"name": "नाम",
|
||||||
@@ -649,124 +649,6 @@
|
|||||||
"address1": "पता 1",
|
"address1": "पता 1",
|
||||||
"address2": "पता 2",
|
"address2": "पता 2",
|
||||||
"city": "शहर",
|
"city": "शहर",
|
||||||
"relationwithnominee": "नामांकित व्यक्ति के साथ संबंध",
|
|
||||||
"policyStatus": "पॉलिसी की स्थिति",
|
|
||||||
"emailId": "ईमेल आईडी",
|
|
||||||
"customerFirstName": "ग्राहक का पहला नाम",
|
|
||||||
"customerMiddleName": "ग्राहक का मध्य नाम",
|
|
||||||
"customerLastName": "ग्राहक का अंतिम नाम",
|
|
||||||
"pensionAmountOptedFor": "चुनी गई पेंशन राशि",
|
|
||||||
"monthlyContribution": "मासिक योगदान",
|
|
||||||
"ageOfJoining": "शामिल होने की आयु",
|
|
||||||
"nameOfSpouse": "पति/पत्नी का नाम",
|
|
||||||
"isIncomeTaxPayer": "क्या आयकर दाता है",
|
|
||||||
"isBeneficiaryOtherScheme": "अन्य सामाजिक सुरक्षा योजनाओं के लाभार्थी",
|
|
||||||
"relationWithSubscriber": "अभिदाता के साथ संबंध",
|
|
||||||
"secondNomineeName": "दूसरे नामांकित व्यक्ति का नाम",
|
|
||||||
"relationWithNominee": "नामांकित व्यक्ति के साथ संबंध",
|
"relationWithNominee": "नामांकित व्यक्ति के साथ संबंध",
|
||||||
"secondNomineeRelationship": "दूसरे नामांकित व्यक्ति का संबंध",
|
"policyStatus": "पॉलिसी की स्थिति"
|
||||||
"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" : "नया डिपॉजिट बनाएं"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ void main() async {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Check for device compromise
|
// Check for device compromise
|
||||||
// final compromisedMessage = await SecurityService.deviceCompromisedMessage;
|
final compromisedMessage = await SecurityService.deviceCompromisedMessage;
|
||||||
// if (compromisedMessage != null) {
|
if (compromisedMessage != null) {
|
||||||
// runApp(MaterialApp(
|
runApp(MaterialApp(
|
||||||
// home: SecurityErrorScreen(message: compromisedMessage),
|
home: SecurityErrorScreen(message: compromisedMessage),
|
||||||
// ));
|
));
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
await setupDependencies();
|
await setupDependencies();
|
||||||
runApp(const KMobile());
|
runApp(const KMobile());
|
||||||
}
|
}
|
||||||
|
|||||||
26
pubspec.lock
26
pubspec.lock
@@ -765,14 +765,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
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:
|
screenshot:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -781,6 +773,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
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:
|
share_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -869,6 +869,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
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:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -1011,7 +1019,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.4"
|
version: "3.1.4"
|
||||||
uuid:
|
uuid:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: uuid
|
name: uuid
|
||||||
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.6
|
cupertino_icons: ^1.0.6
|
||||||
jailbreak_root_detection: ^1.1.6
|
jailbreak_root_detection: ^1.1.6
|
||||||
equatable: ^2.0.7
|
equatable: ^2.0.7
|
||||||
dio: ^5.8.0+1
|
dio: ^5.9.0
|
||||||
flutter_secure_storage: ^9.2.4
|
flutter_secure_storage: ^9.2.4
|
||||||
bloc: ^9.0.0
|
bloc: ^9.0.0
|
||||||
flutter_bloc: ^9.1.0
|
flutter_bloc: ^9.1.0
|
||||||
@@ -63,7 +63,9 @@ dependencies:
|
|||||||
package_info_plus: ^4.2.0
|
package_info_plus: ^4.2.0
|
||||||
flutter_local_notifications: ^19.5.0
|
flutter_local_notifications: ^19.5.0
|
||||||
open_filex: ^4.7.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"
|
# jailbreak_root_detection: "^1.1.6"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user