14 Commits

Author SHA1 Message Date
6861a4a349 VERIFIED issue fixed 2026-03-20 17:25:55 +05:30
298c0c199f Till PMSBY/JJBY test sim binded apk release 2026-03-16 16:39:31 +05:30
dfdc293309 Yojna & Cheque done 2026-03-12 13:44:13 +05:30
1da7574ddb Yojna Service added to dio 2026-03-12 12:25:03 +05:30
30a45015d0 Localizations pasted 2026-03-12 12:19:40 +05:30
d8ebd0ed0e Beneficiary details changes, cheque and yojna services 2026-03-12 12:11:30 +05:30
89569ab1c3 Not verified case added 2026-01-16 15:49:40 +05:30
5c959ba15c VERIFIED message fixed 2026-01-16 15:18:08 +05:30
d89a4f5109 Test APK with Sim Binding and Cheque 2026-01-16 13:53:03 +05:30
1c3a07bd66 Test APK with cheque #2 2025-12-31 13:52:37 +05:30
d44ee5590e Test APK with cheque 2025-12-31 13:45:19 +05:30
715162b713 Test Sim Binding APK 2025-12-11 12:18:25 +05:30
8149ef2a5b Sim Binding Done #1 2025-12-10 11:50:02 +05:30
1a2dea611b SMS integrated with new ui 2025-12-09 18:11:46 +05:30
54 changed files with 6792 additions and 197 deletions

View File

@@ -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}"

View File

@@ -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(

View File

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

View File

@@ -0,0 +1,191 @@
import 'package:dio/dio.dart';
class Cheque {
final String? type;
final String? InstrType;
final String? Date;
final String? branchCode;
final String? fromCheque;
final String? toCheque;
final String? Chequescount;
final String? ChequeNumber;
final String? transactionCode;
final int? amount;
final String? status;
final String? stopIssueDate;
final String? StopExpiryDate;
Cheque({
this.type,
this.InstrType,
this.Date,
this.branchCode,
this.fromCheque,
this.toCheque,
this.Chequescount,
this.ChequeNumber,
this.transactionCode,
this.amount,
this.status,
this.stopIssueDate,
this.StopExpiryDate,
});
factory Cheque.fromJson(Map<String, dynamic> json) {
return Cheque(
type: json['type'] ?? '',
InstrType: json['InstrType'] ?? '',
Date: json['Date'] ?? '',
branchCode: json['branchCode'] ?? '',
fromCheque: json['fromCheque'] ?? '',
toCheque: json['toCheque'] ?? '',
Chequescount: json['Chequescount'] ?? '',
ChequeNumber: json['ChequeNumber'] ?? '',
transactionCode: json['transactionCode'] ?? '',
amount: json['amount'],
status: json['status'] ?? '',
stopIssueDate: json['stopIssueDate'] ?? '',
StopExpiryDate: json['StopExpiryDate'] ?? '',
);
}
static List<Cheque> listFromJson(List<dynamic> jsonList) {
final chequeList =
jsonList.map((cheque) => Cheque.fromJson(cheque)).toList();
return chequeList;
}
}
class ChequeService {
final Dio _dio;
ChequeService(this._dio);
Future<List<Cheque>> ChequeEnquiry({
required String accountNumber,
required String instrType,
}) async {
try {
final response = await _dio.get(
"/api/cheque/enquiry",
queryParameters: {
'accountNumber': accountNumber,
'instrumentType': instrType,
},
options: Options(
headers: {
"Content-Type": "application/json",
},
),
);
if (response.statusCode == 200) {
if (response.data is Map<String, dynamic> &&
response.data.containsKey('records')) {
final records = response.data['records'];
if (records is List) {
return Cheque.listFromJson(records);
}
}
throw Exception(
"Unexpected API response format: 'records' list not found or malformed");
} else {
throw Exception("Failed to fetch");
}
} catch (e) {
print('Error in ChequeEnquiry: $e');
throw e;
}
}
Future stopCheque({
required String accountno,
required String stopFromChequeNo,
required String instrType,
String? stopToChequeNo,
String? stopIssueDate,
String? stopExpiryDate,
String? stopAmount,
String? stopComment,
String? chequeIssueDate,
required String tpin,
}) async {
final response = await _dio.post(
'/api/cheque/stop',
options: Options(
validateStatus: (int? status) => true,
receiveDataWhenStatusError: true,
),
data: {
'accountNumber': accountno,
'stopFromChequeNo': stopFromChequeNo,
'instrumentType': instrType,
'stopToChequeNo': stopToChequeNo,
'stopIssueDate': stopIssueDate,
'stopExpiryDate': stopExpiryDate,
'stopAmount': stopAmount,
'stopComment': stopComment,
'chqIssueDate': chequeIssueDate,
'tpin': tpin,
},
);
return response.toString();
}
Future revokeStop({
required String accountno,
required String removeFromChequeNo,
required String instrType,
String? removeToChequeNo,
String? removeIssueDate,
String? removeExpiryDate,
String? removeAmount,
String? removeComment,
required String tpin,
}) async {
final response = await _dio.post(
'/api/cheque/removeStop',
options: Options(
validateStatus: (int? status) => true,
receiveDataWhenStatusError: true,
),
data: {
'accountNumber': accountno,
'removeFromChequeNo': removeFromChequeNo,
'instrumentType': instrType,
'removeToChequeNo': removeToChequeNo,
'removeIssueDate': removeIssueDate,
'removeExpiryDate': removeExpiryDate,
'removeAmount': removeAmount,
'removeComment': removeComment,
'tpin': tpin,
},
);
return response.toString();
}
Future registerPPS({
required String cheque_no,
required String account_number,
String? issue_date,
String? amount,
String? payee_name,
required String tpin,
}) async {
final response = await _dio.post(
'/api/pps',
options: Options(
validateStatus: (int? status) => true,
receiveDataWhenStatusError: true,
),
data: {
'cheque_no': cheque_no,
'account_number': account_number,
'issue_date': issue_date,
'amount': amount,
'payee_name': payee_name,
'tpin': tpin,
},
);
return response.toString();
}
}

View File

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

View File

@@ -0,0 +1,213 @@
import 'dart:developer';
import 'package:dio/dio.dart';
class YojnaService {
final Dio _dio;
YojnaService(this._dio);
Future<dynamic> fetchpmydetails({
required String scheme,
required String action,
required String accountno,
}) async {
try {
final response = await _dio.post(
"/api/gov-scheme/req/PMJBY",
data: {
'scheme': scheme,
'action': action,
'accountNo': accountno,
},
options: Options(
headers: {
"Content-Type": "application/json",
},
),
);
log("PMY Details Response: ${response.data}");
if (response.statusCode == 200) {
return response.data;
} else {
throw Exception("INTERNAL SERVER ERROR");
}
} catch (e) {
log("Error fetching PMY details: $e");
return null;
}
}
Future secondvalidationPMJJBY({
String? aadharno,
String? accountno,
String? availablebalance,
String? country,
String? customerdob,
String? customername,
String? customerno,
String? dateofacctopening,
String? emailid,
String? financialyear,
String? gender,
String? ifsccode,
String? married,
String? mobileno,
String? pan,
String? pincode,
String? policynumber,
String? premiumamount,
String? state,
String? healthstatus,
String? collectionchannel,
String? nomineename,
String? nomineeaddress,
String? nomineerelationship,
String? nomineeminor,
String? ruralcategory,
}) async {
final response = await _dio.post(
'/api/gov-scheme/create/PMJBY',
options: Options(
validateStatus: (int? status) => true,
receiveDataWhenStatusError: true,
),
data: {
'aadharno': aadharno ,
'accountno': accountno,
'availablebalance': availablebalance,
'country': country,
'customerdob': customerdob,
'customername': customername,
'customerno': customerno,
'dateofacctopening': dateofacctopening,
'emailid': emailid,
'financialyear': financialyear,
'gender': gender,
'ifsccode': ifsccode,
'married': married,
'mobileno': mobileno,
'pan': pan,
'pincode': pincode,
'policynumber': policynumber,
'premiumamount': premiumamount,
'state': state,
'healthstatus': healthstatus,
'collectionchannel': collectionchannel,
'nomineename': nomineename,
'nomineeaddress': nomineeaddress,
'nomineerelationship': nomineerelationship,
'nomineeminor': nomineeminor,
'ruralcategory': ruralcategory,
},
);
return response.toString();
}
Future secondvalidationPMSBY({
String? aadharno,
String? accountno,
String? address1,
String? address2,
String? availablebalance,
String? city,
String? country,
String? customerdob,
String? customername,
String? customerno,
String? emailid,
String? financialyear,
String? gender,
String? married,
String? mobileno,
String? pan,
String? pincode,
String? policyno,
String? premiumamount,
String? state,
String? healthstatus,
String? nomineename,
String? nomineeadress,
String? relationwithnominee,
String? nomineeminor,
String? collectionchannel,
String? ruralcategory,
String? policystatus,
}) async {
final response = await _dio.post(
'/api/gov-scheme/create/PMSBY',
options: Options(
validateStatus: (int? status) => true,
receiveDataWhenStatusError: true,
),
data: {
"aadharno": aadharno,
"accountno": accountno,
"address1": address1,
"address2": address2,
"availablebalance": availablebalance,
"city": city,
"country": country,
"customerdob": customerdob,
"customername": customername,
"customerno": customerno,
"emailid": emailid,
"financialyear": financialyear,
"gender": gender,
"married": married,
"mobileno": mobileno,
"pan": pan,
"pincode": pincode,
"policyno": policyno,
"premiumamount": premiumamount,
"state": state,
"healthstatus": healthstatus,
"nomineename": nomineename,
"nomineeadress": nomineeadress,
"relationwithnominee": relationwithnominee,
"nomineeminor": nomineeminor,
"collectionchannel": collectionchannel,
"ruralcategory": ruralcategory,
"policystatus": policystatus,
},
);
return response.toString();
}
Future<dynamic> enquiry({
required String scheme,
required String action,
required String financialyear,
String? customerno,
}) async {
try {
final response = await _dio.get(
"/api/gov-scheme/enquiry/PMJBY",
queryParameters: {
'scheme': scheme,
'action': action,
'financialyear': financialyear,
'customerno': customerno,
},
options: Options(
headers: {
"Content-Type": "application/json",
},
),
);
log("PMY Details Response: ${response.data}");
if (response.statusCode == 200) {
return response.data;
} else {
throw Exception("INTERNAL SERVER ERROR");
}
} catch (e) {
log("Error fetching PMY details: $e");
return null;
}
}
}

View File

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

View File

@@ -1,5 +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/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';
@@ -9,6 +10,7 @@ import 'package:dio/dio.dart';
import 'package:kmobile/api/services/beneficiary_service.dart'; import 'package:kmobile/api/services/beneficiary_service.dart';
import 'package:kmobile/api/services/payment_service.dart'; import 'package:kmobile/api/services/payment_service.dart';
import 'package:kmobile/api/services/user_service.dart'; import 'package:kmobile/api/services/user_service.dart';
import 'package:kmobile/api/services/yojna_service.dart';
import 'package:kmobile/data/repositories/transaction_repository.dart'; import 'package:kmobile/data/repositories/transaction_repository.dart';
import 'package:kmobile/features/auth/controllers/theme_cubit.dart'; import 'package:kmobile/features/auth/controllers/theme_cubit.dart';
import 'package:kmobile/features/auth/controllers/theme_mode_cubit.dart'; import 'package:kmobile/features/auth/controllers/theme_mode_cubit.dart';
@@ -56,6 +58,8 @@ Future<void> setupDependencies() async {
getIt.registerSingleton<RtgsService>(RtgsService(getIt<Dio>())); getIt.registerSingleton<RtgsService>(RtgsService(getIt<Dio>()));
getIt.registerSingleton<ImpsService>(ImpsService(getIt<Dio>())); getIt.registerSingleton<ImpsService>(ImpsService(getIt<Dio>()));
getIt.registerSingleton<BranchService>(BranchService(getIt<Dio>())); getIt.registerSingleton<BranchService>(BranchService(getIt<Dio>()));
getIt.registerSingleton<ChequeService>(ChequeService(getIt<Dio>()));
getIt.registerSingleton<YojnaService>(YojnaService(getIt<Dio>()));
getIt.registerLazySingleton<ChangePasswordService>( getIt.registerLazySingleton<ChangePasswordService>(
() => ChangePasswordService(getIt<Dio>()), () => ChangePasswordService(getIt<Dio>()),
); );
@@ -74,9 +78,9 @@ 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:8080', //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),
receiveTimeout: const Duration(seconds: 60), receiveTimeout: const Duration(seconds: 60),
headers: { headers: {

View File

@@ -0,0 +1,175 @@
import 'package:flutter/material.dart'; // Keep if User model is generic
import 'package:kmobile/features/account_opening/screens/fd_screen.dart';
import 'package:kmobile/features/account_opening/screens/loan_screen.dart';
import 'package:kmobile/features/account_opening/screens/rd_screen.dart';
import 'package:kmobile/features/account_opening/screens/td_screen.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import '../../../l10n/app_localizations.dart';
class AccountOpeningScreen extends StatefulWidget {
const AccountOpeningScreen({
super.key,
});
@override
State<AccountOpeningScreen> createState() => _AccountOpeningScreenState();
}
class _AccountOpeningScreenState extends State<AccountOpeningScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(
"Account Opening",
),
centerTitle: false,
),
body: Stack(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: AccountOpeningCardTile(
icon: Symbols.savings,
label: "Fixed Deposit (FD)",
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const FdScreen(
),
),
);
},
),
),
Expanded(
child: AccountOpeningCardTile(
icon: Symbols.currency_rupee,
label: "Term Deposit",
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const TermDepositScreen()
),
);
},
),
),
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,
),
),
],
),
),
),
),
),
);
}
}

View 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),
),
),
),
],
),
),
);
}
}

View File

@@ -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.'),
),
);
}
}

View 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"),
),
);
}
}

View 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"),
),
);
}
}

View 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)"),
),
);
}
}

View File

@@ -3,6 +3,7 @@ import 'package:kmobile/app.dart';
import 'package:kmobile/features/auth/screens/mpin_screen.dart'; import 'package:kmobile/features/auth/screens/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,14 +31,25 @@ class LoginScreenState extends State<LoginScreen>
super.dispose(); super.dispose();
} }
void _submitForm() { void _submitForm() async {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
final bool? verificationSuccess = await Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => VerificationScreen(
customerNo: _customerNumberController.text.trim(),
password: _passwordController.text,
),
),
);
if (verificationSuccess == true && mounted) {
context.read<AuthCubit>().login( context.read<AuthCubit>().login(
_customerNumberController.text.trim(), _customerNumberController.text.trim(),
_passwordController.text, _passwordController.text,
); );
} }
} }
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

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

View File

@@ -0,0 +1,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'),
),
]
],
),
),
),
);
}
}

View File

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

View File

@@ -147,7 +147,9 @@ class _ManageBeneficiariesScreen extends State<ManageBeneficiariesScreen> {
child: TextField( child: TextField(
controller: _searchController, controller: _searchController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search by name or account number", hintText:
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),

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,361 @@
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/cheque_service.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class ChequeEnquiryScreen extends StatefulWidget {
final List<User> users;
final int selectedIndex;
const ChequeEnquiryScreen({
super.key,
required this.users,
required this.selectedIndex,
});
@override
State<ChequeEnquiryScreen> createState() => _ChequeEnquiryScreenState();
}
class _ChequeEnquiryScreenState extends State<ChequeEnquiryScreen> {
User? _selectedAccount;
final TextEditingController _searchController = TextEditingController();
var service = getIt<ChequeService>();
bool _isLoading = true;
List<Cheque> _allCheques = [];
Map<String, List<Cheque>> _groupedCheques = {};
List<User> _filteredUsers = [];
@override
void initState() {
super.initState();
_filteredUsers = widget.users
.where((user) => ['SA', 'SB', 'CA', 'CC'].contains(user.accountType))
.toList();
if (widget.users.isNotEmpty && widget.selectedIndex < widget.users.length) {
if (_filteredUsers.isNotEmpty) {
if (_filteredUsers.contains(widget.users[widget.selectedIndex])) {
_selectedAccount = widget.users[widget.selectedIndex];
} else {
_selectedAccount = _filteredUsers.first;
}
} else {
_selectedAccount = widget.users[widget.selectedIndex];
}
} else {
if (_filteredUsers.isNotEmpty) {
_selectedAccount = _filteredUsers.first;
}
}
_loadCheques();
_searchController.addListener(() {
_filterCheques(_searchController.text);
});
}
Future<void> _loadCheques() async {
if (_selectedAccount == null) {
setState(() {
_isLoading = false;
_groupedCheques = {};
});
return;
}
setState(() {
_isLoading = true;
});
String instrType;
switch (_selectedAccount!.accountType) {
case 'SA':
case 'SB':
instrType = '10';
break;
case 'CA':
instrType = '11';
break;
case 'CC':
instrType = '13';
break;
default:
instrType = '10';
}
try {
final data = await service.ChequeEnquiry(
accountNumber: _selectedAccount!.accountNo!, instrType: instrType);
_allCheques = data;
_groupedCheques.clear();
for (var cheque in _allCheques) {
if (cheque.type != null) {
if (!_groupedCheques.containsKey(cheque.type)) {
_groupedCheques[cheque.type!] = [];
}
_groupedCheques[cheque.type!]!.add(cheque);
}
}
setState(() {
_isLoading = false;
});
} catch (e) {
setState(() {
_isLoading = false;
_groupedCheques = {};
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to fetch cheque status: ${e.toString()}'),
),
);
}
}
void _filterCheques(String query) {
_groupedCheques.clear();
List<Cheque> filteredCheques;
if (query.isEmpty) {
filteredCheques = _allCheques;
} else {
filteredCheques = _allCheques.where((cheque) {
final lowerQuery = query.toLowerCase();
return (cheque.ChequeNumber?.toLowerCase().contains(lowerQuery) ??
false) ||
(cheque.status?.toLowerCase().contains(lowerQuery) ?? false) ||
(cheque.fromCheque?.toLowerCase().contains(lowerQuery) ?? false) ||
(cheque.toCheque?.toLowerCase().contains(lowerQuery) ?? false);
}).toList();
}
for (var cheque in filteredCheques) {
if (cheque.type != null) {
if (!_groupedCheques.containsKey(cheque.type)) {
_groupedCheques[cheque.type!] = [];
}
_groupedCheques[cheque.type!]!.add(cheque);
}
}
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context).chequeEnquiryTitle),
centerTitle: false,
),
body: Stack(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Card(
elevation: 4,
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
AppLocalizations.of(context).accountNumber,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 18),
),
const SizedBox(width: 16),
if (_selectedAccount != null)
Expanded(
child: DropdownButton<User>(
value: _selectedAccount,
onChanged: (User? newUser) {
if (newUser != null) {
setState(() {
_selectedAccount = newUser;
_loadCheques();
});
}
},
items: _filteredUsers.map((user) {
return DropdownMenuItem<User>(
value: user,
child: Text(user.accountNo.toString()),
);
}).toList(),
),
)
else
const Text('No accounts found'),
],
),
),
),
const SizedBox(height: 20),
Card(
elevation: 4,
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: _searchController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context)
.searchByChequeDetailsHint,
prefixIcon: const Icon(Icons.search),
border: InputBorder
.none, // Remove border to make it look like it's inside the card
),
),
),
),
const SizedBox(height: 20),
Expanded(
child: _isLoading
? const Center(child: CircularProgressIndicator())
: _groupedCheques.isEmpty
? Center(
child: Text(AppLocalizations.of(context)
.noChequeStatusFound))
: ListView(
children: _groupedCheques.entries.map((entry) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...entry.value.map((cheque) =>
ChequeStatusTile(cheque: cheque)),
],
);
}).toList(),
),
),
],
),
),
IgnorePointer(
child: Center(
child: Opacity(
opacity: 0.07, // Reduced opacity
child: ClipOval(
child: Image.asset(
'assets/images/logo.png',
width: 200, // Adjust size as needed
height: 200, // Adjust size as needed
),
),
),
),
),
],
),
);
}
}
class ChequeStatusTile extends StatelessWidget {
final Cheque cheque;
const ChequeStatusTile({
super.key,
required this.cheque,
});
@override
Widget build(BuildContext context) {
switch (cheque.type) {
case 'CI':
return _buildCiTile(context);
case 'PR':
return _buildPrTile(context);
case 'ST':
return _buildStTile(context);
default:
return const SizedBox.shrink();
}
}
Widget _buildCiTile(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(
vertical: 8.0,
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(AppLocalizations.of(context).chequebookIssuedLabel,
style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 8),
_buildInfoRow('Branch Code:', cheque.branchCode),
_buildInfoRow('From Cheque:', cheque.fromCheque),
_buildInfoRow('To Cheque:', cheque.toCheque),
_buildInfoRow('Date:', cheque.Date),
_buildInfoRow('Cheques Count:', cheque.Chequescount),
],
),
),
);
}
Widget _buildPrTile(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(AppLocalizations.of(context).presentedChequeLabel,
style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 8),
_buildInfoRow('Branch Code:', cheque.branchCode),
_buildInfoRow('Cheque Number:', cheque.ChequeNumber),
_buildInfoRow('Date:', cheque.Date),
_buildInfoRow('Transaction Code:', cheque.transactionCode),
_buildInfoRow('Amount:', '${cheque.amount.toString()}'),
_buildInfoRow('Status:', cheque.status),
],
),
),
);
}
Widget _buildStTile(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(AppLocalizations.of(context).stopChequeLabel,
style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 8),
_buildInfoRow('Branch Code:', cheque.branchCode),
_buildInfoRow('From Cheque:', cheque.fromCheque),
_buildInfoRow('To Cheque:', cheque.toCheque),
_buildInfoRow('Stop Issue Date:', cheque.stopIssueDate),
_buildInfoRow('Stop Expiry Date:', cheque.StopExpiryDate),
_buildInfoRow('Cheques Count:', cheque.Chequescount),
],
),
),
);
}
Widget _buildInfoRow(String label, String? value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(fontWeight: FontWeight.bold)),
Text(value ?? ''),
],
),
);
}
}

View File

@@ -1,16 +1,29 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:kmobile/features/enquiry/screens/enquiry_screen.dart'; import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/features/cheque/screens/cheque_enquiry_screen.dart';
import 'package:kmobile/features/cheque/screens/positive_pay_screen.dart';
import 'package:kmobile/features/cheque/screens/revoke_stop_screen.dart';
import 'package:kmobile/features/cheque/screens/stop_cheque_screen.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart';
import '../../../l10n/app_localizations.dart'; import '../../../l10n/app_localizations.dart';
class ChequeManagementScreen extends StatefulWidget { class ChequeManagementScreen extends StatefulWidget {
const ChequeManagementScreen({super.key}); final List<User> users;
final int selectedIndex;
const ChequeManagementScreen({
super.key,
required this.users,
required this.selectedIndex,
});
@override @override
State<ChequeManagementScreen> createState() => _ChequeManagementScreen(); State<ChequeManagementScreen> createState() => _ChequeManagementScreen();
} }
class _ChequeManagementScreen extends State<ChequeManagementScreen> { class _ChequeManagementScreen extends State<ChequeManagementScreen> {
List<User> get users => widget.users;
int get selectedAccountIndex => widget.selectedIndex;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@@ -22,53 +35,82 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
), ),
body: Stack( body: Stack(
children: [ children: [
ListView( Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
const SizedBox(height: 15), Expanded(
ChequeManagementTile( child: ChequeManagementCardTile(
icon: Symbols.add, icon: Symbols.payments,
label: AppLocalizations.of(context).requestChequeBook, label: AppLocalizations.of(context).chequeEnquiryTitle,
onTap: () {},
),
Divider(height: 1, color: Theme.of(context).dividerColor),
ChequeManagementTile(
icon: Symbols.data_alert,
label: AppLocalizations.of(context).enquiry,
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const EnquiryScreen()), builder: (context) => ChequeEnquiryScreen(
users: users,
selectedIndex: selectedAccountIndex,
),
),
); );
}, },
), ),
Divider(height: 1, color: Theme.of(context).dividerColor),
ChequeManagementTile(
icon: Symbols.approval_delegation,
label: AppLocalizations.of(context).chequeDeposit,
onTap: () {},
), ),
Divider(height: 1, color: Theme.of(context).dividerColor), Expanded(
ChequeManagementTile( child: ChequeManagementCardTile(
icon: Symbols.front_hand, icon: Symbols.block_sharp,
label: AppLocalizations.of(context).stopCheque, label: AppLocalizations.of(context).stopCheque,
onTap: () {}, onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StopChequeScreen(
users: users,
selectedIndex: selectedAccountIndex,
), ),
Divider(height: 1, color: Theme.of(context).dividerColor), ),
ChequeManagementTile( );
icon: Symbols.cancel_presentation, },
),
),
Expanded(
child: ChequeManagementCardTile(
icon: Symbols.stop_circle,
label: AppLocalizations.of(context).revokeStop, label: AppLocalizations.of(context).revokeStop,
onTap: () {}, onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RevokeStopChequeScreen(
users: users,
selectedIndex: selectedAccountIndex,
),
),
);
},
),
),
Expanded(
child: ChequeManagementCardTile(
icon: Symbols.check_circle, // Using check_circle for Positive Pay
label: AppLocalizations.of(context).positivePayTitle,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PositivePayScreen(
users: users,
selectedIndex: selectedAccountIndex,
),
),
);
},
), ),
Divider(height: 1, color: Theme.of(context).dividerColor),
ChequeManagementTile(
icon: Symbols.payments,
label: AppLocalizations.of(context).positivePay,
onTap: () {},
), ),
Divider(height: 1, color: Theme.of(context).dividerColor),
], ],
), ),
),
IgnorePointer( IgnorePointer(
child: Center( child: Center(
child: Opacity( child: Opacity(
@@ -89,25 +131,64 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
} }
} }
class ChequeManagementTile extends StatelessWidget { class ChequeManagementCardTile extends StatelessWidget {
final IconData icon; final IconData icon;
final String label; final String label;
final VoidCallback onTap; final VoidCallback onTap;
final bool disable;
const ChequeManagementTile({ const ChequeManagementCardTile({
super.key, super.key,
required this.icon, required this.icon,
required this.label, required this.label,
required this.onTap, required this.onTap,
this.disable = false,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListTile( final theme = Theme.of(context);
leading: Icon(icon), return Card(
title: Text(label), margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
trailing: const Icon(Symbols.arrow_right, size: 20), shape: RoundedRectangleBorder(
onTap: onTap, borderRadius: BorderRadius.circular(12.0),
),
elevation: 4, // Add some elevation for better visual separation
child: InkWell(
onTap:
disable ? null : onTap, // Disable InkWell if the tile is disabled
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, // Make icon larger
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,
),
),
],
),
),
),
),
),
); );
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,13 +8,14 @@ import 'package:kmobile/features/accounts/screens/account_statement_screen.dart'
import 'package:kmobile/features/accounts/screens/all_accounts_screen.dart'; import 'package:kmobile/features/accounts/screens/all_accounts_screen.dart';
import 'package:kmobile/features/auth/controllers/auth_cubit.dart'; import 'package:kmobile/features/auth/controllers/auth_cubit.dart';
import 'package:kmobile/features/auth/controllers/auth_state.dart'; import 'package:kmobile/features/auth/controllers/auth_state.dart';
import 'package:kmobile/features/cheque/screens/cheque_management_screen.dart';
import 'package:kmobile/features/customer_info/screens/customer_info_screen.dart'; import 'package:kmobile/features/customer_info/screens/customer_info_screen.dart';
import 'package:kmobile/features/beneficiaries/screens/manage_beneficiaries_screen.dart'; import 'package:kmobile/features/beneficiaries/screens/manage_beneficiaries_screen.dart';
import 'package:kmobile/features/enquiry/screens/enquiry_screen.dart';
import 'package:kmobile/features/fund_transfer/screens/fund_transfer_screen.dart'; import 'package:kmobile/features/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/service/screens/branch_locator_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/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';
import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart';
@@ -443,7 +444,8 @@ class _DashboardScreenState extends State<DashboardScreen>
final accountType = currAccount.accountType?.toLowerCase(); final accountType = currAccount.accountType?.toLowerCase();
final isPaymentDisabled = accountType != 'sa' && final isPaymentDisabled = accountType != 'sa' &&
accountType != 'sb' && accountType != 'sb' &&
accountType != 'ca'; accountType != 'ca' &&
accountType != 'cc';
// firsttime load // firsttime load
if (!_txInitialized) { if (!_txInitialized) {
_txInitialized = true; _txInitialized = true;
@@ -634,29 +636,29 @@ class _DashboardScreenState extends State<DashboardScreen>
ManageBeneficiariesScreen( ManageBeneficiariesScreen(
customerName: currAccount.name!))); customerName: currAccount.name!)));
}, disable: false), }, disable: false),
_buildQuickLink(Symbols.support_agent, _buildQuickLink(Symbols.family_group,
AppLocalizations.of(context).contactUs, () { AppLocalizations.of(context).governmentSchemes, () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) =>
const EnquiryScreen())); GovSchemeScreen(users: users,
selectedIndex: selectedAccountIndex)));
}), }),
_buildQuickLink( _buildQuickLink(
Symbols.person, Symbols.checkbook,
AppLocalizations.of(context).profile, AppLocalizations.of(context).chequeManagement,
() { () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => ProfileScreen( builder: (context) => ChequeManagementScreen(
mobileNumber: mobileNumberToPass, users: users,
customerNo: customerNo, selectedIndex: selectedAccountIndex),
customerName: customerName),
), ),
); );
}, },
disable: false, disable: isPaymentDisabled,
), ),
], ],
), ),

View File

@@ -496,7 +496,7 @@ class _FundTransferAmountScreenState extends State<FundTransferAmountScreen> {
Text(AppLocalizations.of(context).fetchingDailyLimit), Text(AppLocalizations.of(context).fetchingDailyLimit),
if (!_isLoadingLimit && _limit != null) if (!_isLoadingLimit && _limit != null)
Text( Text(
'Remaining Daily Limit: ${_formatCurrency.format(_limit!.dailyLimit - _limit!.usedLimit)}', '${AppLocalizations.of(context).remainingDailyLimit} ${_formatCurrency.format(_limit!.dailyLimit - _limit!.usedLimit)}',
style: Theme.of(context).textTheme.bodySmall, style: Theme.of(context).textTheme.bodySmall,
), ),
const Spacer(), const Spacer(),

View File

@@ -198,7 +198,8 @@ class _FundTransferBeneficiaryScreenState
child: TextField( child: TextField(
controller: _searchController, controller: _searchController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search by name or account number", hintText:
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),

View File

@@ -42,7 +42,7 @@ class FundTransferScreen extends StatelessWidget {
Expanded( Expanded(
child: FundTransferManagementTile( child: FundTransferManagementTile(
icon: Symbols.person, icon: Symbols.person,
label: "Self Pay", label: AppLocalizations.of(context).selfPay,
subtitle: subtitle:
AppLocalizations.of(context).ftselfpaysubtitle, AppLocalizations.of(context).ftselfpaysubtitle,
onTap: () { onTap: () {

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:kmobile/data/models/user.dart'; import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/features/fund_transfer/screens/fund_transfer_self_amount_screen.dart'; import 'package:kmobile/features/fund_transfer/screens/fund_transfer_self_amount_screen.dart';
import 'package:kmobile/l10n/app_localizations.dart';
import 'package:kmobile/widgets/bank_logos.dart'; import 'package:kmobile/widgets/bank_logos.dart';
class FundTransferSelfAccountsScreen extends StatelessWidget { class FundTransferSelfAccountsScreen extends StatelessWidget {
@@ -43,7 +44,7 @@ class FundTransferSelfAccountsScreen extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text("Select Account"), title: Text(AppLocalizations.of(context).selectAccount),
), ),
body: Stack( body: Stack(
children: [ children: [

View File

@@ -7,6 +7,7 @@ import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/di/injection.dart'; import 'package:kmobile/di/injection.dart';
import 'package:kmobile/features/fund_transfer/screens/payment_animation.dart'; import 'package:kmobile/features/fund_transfer/screens/payment_animation.dart';
import 'package:kmobile/features/fund_transfer/screens/transaction_pin_screen.dart'; import 'package:kmobile/features/fund_transfer/screens/transaction_pin_screen.dart';
import 'package:kmobile/l10n/app_localizations.dart';
import 'package:kmobile/widgets/bank_logos.dart'; import 'package:kmobile/widgets/bank_logos.dart';
class FundTransferSelfAmountScreen extends StatefulWidget { class FundTransferSelfAmountScreen extends StatefulWidget {
@@ -134,7 +135,7 @@ class _FundTransferSelfAmountScreenState
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text("Fund Transfer"), title: Text(AppLocalizations.of(context).fundTransferTitle),
), ),
body: SafeArea( body: SafeArea(
child: Stack( child: Stack(
@@ -148,7 +149,7 @@ class _FundTransferSelfAmountScreenState
children: [ children: [
// Debit Account (User) // Debit Account (User)
Text( Text(
"Debit From", AppLocalizations.of(context).debitFromLabel,
style: Theme.of(context).textTheme.titleSmall, style: Theme.of(context).textTheme.titleSmall,
), ),
Card( Card(
@@ -168,7 +169,7 @@ class _FundTransferSelfAmountScreenState
// Credit Account (Self) // Credit Account (Self)
Text( Text(
"Credited To", AppLocalizations.of(context).creditedTo,
style: Theme.of(context).textTheme.titleSmall, style: Theme.of(context).textTheme.titleSmall,
), ),
Card( Card(
@@ -186,9 +187,10 @@ class _FundTransferSelfAmountScreenState
// Remarks // Remarks
TextFormField( TextFormField(
controller: _remarksController, controller: _remarksController,
decoration: const InputDecoration( decoration: InputDecoration(
labelText: "Remarks (Optional)", labelText:
border: OutlineInputBorder(), AppLocalizations.of(context).remarksOptionalHint,
border: const OutlineInputBorder(),
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
@@ -197,18 +199,18 @@ class _FundTransferSelfAmountScreenState
TextFormField( TextFormField(
controller: _amountController, controller: _amountController,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
decoration: const InputDecoration( decoration: InputDecoration(
labelText: "Amount", labelText: AppLocalizations.of(context).amountLabel,
border: OutlineInputBorder(), border: const OutlineInputBorder(),
prefixIcon: Icon(Icons.currency_rupee), prefixIcon: const Icon(Icons.currency_rupee),
), ),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return "Amount is required"; return AppLocalizations.of(context).amountRequired;
} }
if (double.tryParse(value) == null || if (double.tryParse(value) == null ||
double.parse(value) <= 0) { double.parse(value) <= 0) {
return "Please enter a valid amount"; return AppLocalizations.of(context).validAmountError;
} }
return null; return null;
}, },
@@ -216,10 +218,12 @@ class _FundTransferSelfAmountScreenState
const SizedBox(height: 8), const SizedBox(height: 8),
// Daily Limit Display // Daily Limit Display
if (_isLoadingLimit) const Text('Fetching daily limit...'), if (_isLoadingLimit)
Text(AppLocalizations.of(context)
.fetchingDailyLimitLoader),
if (!_isLoadingLimit && _limit != null) if (!_isLoadingLimit && _limit != null)
Text( Text(
'Remaining Daily Limit: ${_formatCurrency.format(_limit!.dailyLimit - _limit!.usedLimit)}', '${AppLocalizations.of(context).remainingDailyLimit} ${_formatCurrency.format(_limit!.dailyLimit - _limit!.usedLimit)}',
style: Theme.of(context).textTheme.bodySmall, style: Theme.of(context).textTheme.bodySmall,
), ),
const Spacer(), const Spacer(),
@@ -232,7 +236,7 @@ class _FundTransferSelfAmountScreenState
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
), ),
child: const Text("Proceed"), child: Text(AppLocalizations.of(context).proceedButton),
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),

View File

@@ -83,8 +83,8 @@ class _DailyLimitScreenState extends State<DailyLimitScreen> {
if (value > 200000) { if (value > 200000) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: const Text( content:
"Limit To be Set must be less than 200000"), Text(localizations.limitToBeSetMustBeLessThan200000),
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,
backgroundColor: theme.colorScheme.error, backgroundColor: theme.colorScheme.error,
), ),
@@ -184,7 +184,7 @@ class _DailyLimitScreenState extends State<DailyLimitScreen> {
if (_currentLimit != null) ...[ if (_currentLimit != null) ...[
const SizedBox(height: 24), const SizedBox(height: 24),
Text( Text(
"Remaining Limit Today", // This should be localized localizations.remainingLimitToday, // This should be localized
style: theme.textTheme.titleMedium, style: theme.textTheme.titleMedium,
), ),
const SizedBox(height: 4), const SizedBox(height: 4),

View File

@@ -76,7 +76,8 @@ class _ATMLocatorScreenState extends State<ATMLocatorScreen> {
onChanged: onChanged:
_filterAtms, // Updated: Call _filterAtms on text change _filterAtms, // Updated: Call _filterAtms on text change
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Name/Address", // Hint text for the search bar hintText: AppLocalizations.of(context)
.nameAddress, // Hint text for the search bar
prefixIcon: const Icon(Icons.search), // Search icon prefixIcon: const Icon(Icons.search), // Search icon
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@@ -90,9 +91,9 @@ class _ATMLocatorScreenState extends State<ATMLocatorScreen> {
child: _isLoading child: _isLoading
? _buildShimmerList() // Display shimmer while loading ? _buildShimmerList() // Display shimmer while loading
: _filteredAtms.isEmpty : _filteredAtms.isEmpty
? const Center( ? Center(
child: Text( child: Text(AppLocalizations.of(context)
"No matching ATMs found")) // Message if no ATMs found .noMatchingAtmsFound)) // Message if no ATMs found
: ListView.builder( : ListView.builder(
itemCount: _filteredAtms itemCount: _filteredAtms
.length, // Number of items in the filtered list .length, // Number of items in the filtered list

View File

@@ -69,7 +69,7 @@ class _BranchLocatorScreenState extends State<BranchLocatorScreen> {
controller: _searchController, controller: _searchController,
onChanged: _filterBranches, // Updated onChanged: _filterBranches, // Updated
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Branch Name", hintText: AppLocalizations.of(context).searchbranch,
prefixIcon: const Icon(Icons.search), prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@@ -83,9 +83,9 @@ class _BranchLocatorScreenState extends State<BranchLocatorScreen> {
child: _isLoading child: _isLoading
? _buildShimmerList() // Changed to shimmer ? _buildShimmerList() // Changed to shimmer
: _filteredBranches.isEmpty : _filteredBranches.isEmpty
? const Center( ? Center(
child: Text( child: Text(AppLocalizations.of(context)
"No matching branches found")) // Updated tex .noMatchingBranchesFound)) // Updated tex
: ListView.builder( : ListView.builder(
itemCount: _filteredBranches.length, itemCount: _filteredBranches.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {

View File

@@ -129,7 +129,7 @@ class _EnquiryScreen extends State<EnquiryScreen> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
"Complaint Form", AppLocalizations.of(context).complaintFormTitle,
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,

View File

@@ -1,4 +1,7 @@
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/enquiry_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';
@@ -43,7 +46,6 @@ class _ServiceScreen extends State<ServiceScreen> {
disabled: false, disabled: false,
), ),
), ),
const SizedBox(height: 16),
Expanded( Expanded(
child: ServiceManagementTile( child: ServiceManagementTile(
icon: Symbols.question_mark, icon: Symbols.question_mark,
@@ -57,7 +59,6 @@ class _ServiceScreen extends State<ServiceScreen> {
disabled: false, disabled: false,
), ),
), ),
const SizedBox(height: 16),
Expanded( Expanded(
child: ServiceManagementTile( child: ServiceManagementTile(
icon: Symbols.location_pin, icon: Symbols.location_pin,
@@ -71,6 +72,45 @@ class _ServiceScreen extends State<ServiceScreen> {
disabled: false, disabled: false,
), ),
), ),
// Expanded(
// child: ServiceManagementTile(
// icon: Symbols.box,
// label: "Account Opening",
// onTap: () {
// Navigator.push(
// context,
// MaterialPageRoute(
// 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,
// ),
// ),
Expanded(
child: ServiceManagementTile(
icon: Symbols.support_agent,
label: AppLocalizations.of(context).contactUs,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const EnquiryScreen()));
},
disabled: false,
),
),
// No Spacer() needed here as Expanded children will fill space // No Spacer() needed here as Expanded children will fill space
], ],
), ),
@@ -122,8 +162,8 @@ 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: Padding( child: Center(
padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 16.0), child: SingleChildScrollView(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@@ -148,6 +188,7 @@ class ServiceManagementTile extends StatelessWidget {
), ),
), ),
), ),
),
); );
} }
} }

View File

@@ -0,0 +1,113 @@
import 'package:flutter/material.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class APYScreen extends StatefulWidget {
final List<User> users;
final int selectedIndex;
const APYScreen({
super.key,
required this.users,
required this.selectedIndex,
});
@override
State<APYScreen> createState() => _APYScreenState();
}
class _APYScreenState extends State<APYScreen> {
User? _selectedAccount;
List<User> _filteredUsers = [];
@override
void initState() {
super.initState();
_filteredUsers = widget.users
.where((user) => ['SA', 'SB', 'CA', 'CC'].contains(user.accountType))
.toList();
// Pre-fill the account number if possible
if (widget.users.isNotEmpty && widget.selectedIndex < widget.users.length) {
if (_filteredUsers.isNotEmpty) {
if (_filteredUsers.contains(widget.users[widget.selectedIndex])) {
_selectedAccount = widget.users[widget.selectedIndex];
} else {
_selectedAccount = _filteredUsers.first;
}
} else {
_selectedAccount = widget.users[widget.selectedIndex];
}
} else {
if (_filteredUsers.isNotEmpty) {
_selectedAccount = _filteredUsers.first;
}
}
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(l10n.apyRegistration),
centerTitle: false,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
l10n.apyDescription,
style: Theme.of(context).textTheme.titleMedium,
),
),
),
const SizedBox(height: 16),
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DropdownButtonFormField<User>(
value: _selectedAccount,
decoration: InputDecoration(
labelText: l10n.accountNumber,
border: const OutlineInputBorder(),
contentPadding: const EdgeInsets.symmetric(
vertical: 20, horizontal: 12),
),
items: _filteredUsers.map((user) {
return DropdownMenuItem<User>(
value: user,
child: Text(user.accountNo.toString()),
);
}).toList(),
onChanged: (User? newUser) {
setState(() {
_selectedAccount = newUser;
});
},
validator: (value) {
if (value == null) {
return l10n.accountNumberRequired;
}
return null;
},
),
],
),
),
),
],
),
),
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -416,5 +416,239 @@
"kycdetails": "KYC Details", "kycdetails": "KYC Details",
"viewall": "View All", "viewall": "View All",
"branchlocator": "Branch Locator", "branchlocator": "Branch Locator",
"atmlocator": "ATM Locator" "atmlocator": "ATM Locator",
"limitSetError": "Limit to be set must be less than {maxAmount}",
"genericError": "Error: {errorMessage}",
"limitUpdatedSuccess": "Limit Updated",
"remainingLimitToday": "Remaining Limit Today",
"branchNameHint": "Branch Name",
"noMatchingBranches": "No matching branches found",
"selfPay": "Self Pay",
"savingsAccountType": "Savings",
"amountExceedsDailyLimit": "Amount exceeds remaining daily limit of ",
"incorrectTpinError": "Please Enter the correct TPIN",
"insufficientFundsError": "Your account does not have sufficient balance",
"somethingWentWrongError": "Something Went Wrong",
"currencyINR": "INR",
"remainingDailyLimit": "Remaining Daily Limit:",
"searchByNameOrAccount": "Search by name or account number",
"beneficiaryCooldownMessage": "Beneficiary will be enabled after the cooldown period.",
"notApplicable": "N/A",
"savingsAccountLabel": "Savings Account",
"loanAccountLabel": "Loan Account",
"termDepositLabel": "Term Deposit",
"recurringDepositLabel": "Recurring Deposit",
"currentAccountLabel": "Current Account",
"unknownAccountLabel": "Unknown Account",
"selectAccountTitle": "Select Account",
"noOtherAccounts": "No other accounts found",
"kccbBankName": "Kangra Central Co-operative Bank",
"fundTransferTitle": "Fund Transfer",
"debitFromLabel": "Debit From",
"creditToLabel": "Credited To",
"remarksOptionalHint": "Remarks (Optional)",
"amountLabel": "Amount",
"amountRequiredError": "Amount is required",
"validAmountError": "Please enter a valid amount",
"fetchingDailyLimitLoader": "Fetching daily limit...",
"proceedButton": "Proceed",
"enterKey": "Enter",
"backKey": "back",
"doneKey": "done",
"transactionDate": "On {date}",
"paymentResultPng": "/payment_result.png",
"rubikFont": "Rubik",
"transactionDateLabel": "Date: {date}",
"utrLabel": "UTR: {utr}",
"searchByNameOrAccountHint": "Search by name or account number",
"savingsAccountDropdown": "Savings",
"beneficiaryExistsError": "Beneficiary already exists",
"somethingWentWrongShort": "Something went Wrong",
"currentAccountDropdown": "Current",
"failedToDeleteBeneficiaryError": "Failed to delete beneficiary: {error}",
"notAvailable": "N/A",
"bankNameLabel": "Bank Name",
"accountNumberLabel": "Account Number",
"accountTypeLabel": "Account Type",
"ifscCodeLabel": "IFSC Code",
"branchNameLabel": "Branch Name",
"enquiryEmailSubject": "Enquiry",
"couldNotOpenEmailApp": "Could not open email app for {email}",
"couldNotOpenDialer": "Could not open dialer for {phone}",
"couldNotLaunchUrl": "Could not launch {url}",
"complaintFormUrl": "https://kccbhp.bank.in/complaint-form/",
"complaintFormTitle": "Complaint Form",
"chairmanEmail": "chairman@kccb.in",
"chairmanPhone": "01892-222677",
"mdEmail": "md@kccb.in",
"mdPhone": "01892-224969",
"gmwEmail": "gmw@kccb.in",
"gmwPhone": "01892-223280",
"gmnEmail": "gmn@kccb.in",
"gmnPhone": "01892-224607",
"atmNameAddressHint": "Name/Address",
"noMatchingAtms": "No matching ATMs found",
"faq1Question": "How do I log in to the mobile banking app?",
"faq1Answer": "You can log in using your customer number and password. Biometric login (fingerprint) and MPIN is also available for supported evices.",
"faq2Question": "Is my banking information secure on this app?",
"faq2Answer": "Yes. We use industry-standard encryption and multi-factor authentication to ensure your data is safe.",
"faq3Question": "How can I check my account balance?",
"faq3Answer": "Once logged in, your account balance will be displayed on the home screen. You can also view detailed balances under the “Accounts” section.",
"faq4Question": "Can I transfer money to other bank accounts?",
"faq4Answer": "Yes. You can use NEFT, RTGS or IMPS to transfer funds to any bank account in India.",
"faq5Question": "How do I view my transaction history?",
"faq5Answer": "Click on the “Account Statement” icon under the Home Screen to view recent and past transactions.",
"chequeEnquiryTitle": "Cheque Enquiry",
"chequeEnquirySubtitle": "You can view the status of your issued cheque book, presented cheques and details of stopped cheques including relevant dates, cheque numbers and other essential information",
"stopChequeSubtitle": "Initiate stop for one or more cheques from your issued checkbook. This essential service helps prevent unauthorized transactions and protects against fraud.",
"chequeEnquiryFailedError": "Failed to fetch cheque status: {error}",
"accountNumberTitle": "Account Number",
"noAccountsFound": "No accounts found",
"searchByChequeDetailsHint": "Search by Cheque Details",
"noChequeStatusFound": "No cheque status found.",
"chequebookIssuedLabel": "Chequebook Issued (CI)",
"branchCodeLabel": "Branch Code:",
"fromChequeLabel": "From Cheque:",
"toChequeLabel": "To Cheque:",
"dateLabel": "Date:",
"chequesCountLabel": "Cheques Count:",
"presentedChequeLabel": "Presented Cheque (PR)",
"chequeNumberLabel": "Cheque Number:",
"transactionCodeLabel": "Transaction Code:",
"amountLabelWithColon": "Amount:",
"statusLabel": "Status:",
"stopChequeLabel": "Stop Cheque (ST)",
"stopIssueDateLabel": "Stop Issue Date:",
"stopExpiryDateLabel": "Stop Expiry Date:",
"emptyString": "",
"cashCreditAccountLabel": "Cash Credit Account",
"stopChequeTitle": "Stop Cheque",
"noChequebookToStop": "No cheque book found to stop cheques from.",
"stopSingleChequeButton": "Stop Single Cheque",
"pleaseSelectAccountFirst": "Please select an account first.",
"stopMultipleChequesButton": "Stop Multiple Cheques",
"noChequeIssuedStatus": "No Cheque Issued status found.",
"chequebookDetailsTitle": "Chequebook Details",
"customerNameLabel": "Customer Name:",
"cifNumberLabel": "CIF Number:",
"startingChequeNumberLabel": "Starting Cheque Number:",
"endingChequeNumberLabel": "Ending Cheque Number:",
"issueDateLabel": "Issue Date:",
"numberOfChequesLabel": "Number of Cheques:",
"instrumentTypeLabel": "Instrument Type:",
"closeButton": "Close",
"stopSingleChequeTitle": "Stop Single Cheque",
"chequeNumberHint": "Cheque Number *",
"pleaseEnterChequeNumberError": "Please enter a cheque number",
"invalidChequeNumberFormatError": "Invalid cheque number format",
"chequeNumberRangeError": "Cheque number must be between {from} and {to}",
"instrumentTypeHint": "Instrument Type *",
"stopIssueDateHint": "Stop Issue Date",
"stopExpiryDateHint": "Stop Expiry Date",
"stopAmountHint": "Stop Amount",
"stopCommentHint": "Stop Comment",
"chequebookIssueDateHint": "Chequebook Issue Date",
"successStatus": "Success",
"errorStatus": "Error",
"incorrectTpinErrorMessage": "The TPIN you entered is incorrect. Please try again.",
"internalServerError": "Internal Server Error",
"stopChequeButton": "Stop Cheque",
"stopMultipleChequesTitle": "Stop Multiple Cheques",
"fromChequeNumberHint": "From Cheque Number *",
"toChequeNumberHint": "To Cheque Number *",
"failedToFetchChequeStatus": "Failed to fetch cheque status: {error}",
"revokeStopSubtitle": "Revoke your stopped cheques so as to reuse it",
"positivePaySubtitle": "Prevent unauthorized use of your issued cheque",
"positivePayTitle": "Positive Pay",
"pleaseEnterAccountNumber": "Please enter account number",
"chequeNumber": "Cheque Number",
"pleaseEnterChequeNumber": "Please enter cheque number",
"chequeIssuedDate": "Cheque Issued Date",
"pleaseSelectChequeIssuedDate": "Please select cheque issued date",
"payeeName": "Payee Name",
"pleaseEnterAmount": "Please enter the amount",
"processingData": "Processing Data",
"revokeStopCheque": "Revoke Stop Cheque",
"currentAccount": "Current Account",
"cashCreditAccount": "Cash Credit Account",
"revokeSingleStop": "Revoke Single Stop",
"noStoppedChequesPresent": "No stopped cheques present",
"revokeMultipleStops": "Revoke Multiple Stops",
"revokeSingleStopTitle": "Revoke Single Stop",
"chequeFound": "Cheque Found",
"chequeFixed": "Cheque Fixed",
"other": "Other",
"revokeIssueDate": "Revoke Issue Date",
"revokeExpiryDate": "Revoke Expiry Date",
"revokeAmount": "Revoke Amount",
"revokeComment": "Revoke Comment",
"otherReasons": "Other Reasons :",
"revokeStopButton": "Revoke Stop",
"invalidTpin": "Invalid TPIN",
"incorrectTpinMessage": "The TPIN you entered is incorrect. Please try again.",
"chequeAlreadyStopped": "The selected Cheque is already stopped",
"chequeAlreadyPresented": "The selected Cheque is already presented",
"chequeLost": "Cheque Lost",
"chequeStolen": "Cheque Stolen",
"chequeMissing": "Cheque Missing",
"chequeDamaged": "Cheque Damaged",
"close": "Close",
"governmentSchemes": "Government Schemes",
"pradhanMantriYojana": "Pradhan Mantri Yojana",
"atalPensionYojana": "Atal Pension Yojana",
"registerForAtalPensionYojana": "Register for Atal Pension Yojana",
"secureYourFutureAPY": "Secure your future with Atal Pension Yojana (APY) retirement scheme. Register in a few steps.",
"enrollPMJJBYPMSBY": "Enroll in Pradhan Mantri Jeevan Jyoti Bima Yojana (PMJJBY) and Pradhan Mantri Suraksha Bima Yojana (PMSBY) insurance schemes",
"apyRegistration": "APY registration",
"apyDescription": "Atal Pension Yojana (APY) is a periodic contribution-based pension scheme for citizens of India.",
"pmjjbyPmsbyDescription": "Create and Enquire about your Pradhan Mantri Jeevan Jyoti Bima Yojana (PMJJBY) and Pradhan Mantri Suraksha Bima Yojana(PMSBY) schemes.",
"selectScheme": "Select Scheme",
"pmjjbyFull": "Pradhan Mantri Jeevan Jyoti Bima Yojana (PMJJBY)",
"pmsbyFull": "Pradhan Mantri Suraksha Bima Yojana (PMSBY)",
"create": "Create",
"pleaseSelectAccountNumber": "Please select account number",
"pleaseSelectSchemeFirst": "Please select a scheme first",
"fetchingDetails": "Fetching details...",
"failedToFetchDetails": "Failed to fetch details or no data found.",
"pmjjbyDetails": "PMJJBY details",
"pmsbyDetails": "PMSBY details",
"cifNumber": "CIF Number",
"selectFinancialYear": "Select Financial Year",
"schemeDetails": "Scheme Details",
"customerName": "Customer Name",
"policyNumber": "Policy Number",
"premiumAmount": "Premium Amount",
"nomineeName": "Nominee Name",
"journalNo": "Journal No",
"noRecordFound": "No record found",
"noDataFoundYear": "No data found for the selected year.",
"pmjjbyRegistration": "PMJJBY Registration",
"pmsbyRegistration": "PMSBY Registration",
"customerNo": "Customer No",
"aadhaarNo": "Aadhaar No",
"customerDobFormat": "Customer DOB (DD/MM/YYYY)",
"gender": "Gender",
"marriedYesNo": "Married (Yes/No)",
"pan": "PAN",
"dateOfAcctOpening": "Date of Acct Opening",
"pincode": "Pincode",
"state": "State",
"country": "Country",
"ruralCategory": "Rural Category",
"policyDetails": "Policy Details",
"financialYear": "Financial Year",
"healthStatus": "Health Status",
"collectionChannel": "Collection Channel",
"nomineeDetails": "Nominee Details",
"nomineeAddress": "Nominee Address",
"nomineeRelationship": "Nominee Relationship",
"nomineeMinor": "Nominee Minor",
"response": "Response",
"recordAlreadyExists": "A record already exists for this request. Your submission has been registered previously.",
"address1": "Address 1",
"address2": "Address 2",
"city": "City",
"relationWithNominee": "Relation with Nominee",
"policyStatus": "Policy Status",
"emailId": "Email ID"
} }

View File

@@ -417,5 +417,238 @@
"kycdetails": "केवाईसी विवरण", "kycdetails": "केवाईसी विवरण",
"viewall": "सभी देखें", "viewall": "सभी देखें",
"branchlocator": "शाखा लोकेटर", "branchlocator": "शाखा लोकेटर",
"atmlocator": "एटीएम लोकेटर" "atmlocator": "एटीएम लोकेटर",
"limitSetError": "निर्धारित की जाने वाली सीमा {maxAmount} से कम होनी चाहिए।",
"genericError": "त्रुटि: {errorMessage}",
"limitUpdatedSuccess": "सीमा अपडेट हो गई",
"remainingLimitToday": "आज की शेष सीमा",
"branchNameHint": "शाखा का नाम",
"noMatchingBranches": "कोई मेल खाने वाली शाखा नहीं मिली",
"selfPay": "स्वयं भुगतान",
"savingsAccountType": "बचत खाता",
"amountExceedsDailyLimit": "राशि की शेष दैनिक सीमा से अधिक है",
"incorrectTpinError": "कृपया सही टीपिन दर्ज करें",
"insufficientFundsError": "आपके खाते में पर्याप्त शेष राशि नहीं है",
"somethingWentWrongError": "कुछ गलत हो गया",
"currencyINR": "INR",
"remainingDailyLimit": "शेष दैनिक सीमा:",
"searchByNameOrAccount": "नाम या खाता संख्या से खोजें",
"beneficiaryCooldownMessage": "कूलडाउन अवधि के बाद लाभार्थी सक्षम हो जाएगा।",
"notApplicable": "लागू नहीं",
"savingsAccountLabel": "बचत खाता",
"loanAccountLabel": "ऋण खाता",
"termDepositLabel": "सावधि जमा",
"recurringDepositLabel": "आवर्ती जमा",
"currentAccountLabel": "चालू खाता",
"unknownAccountLabel": "अज्ञात खाता",
"selectAccountTitle": "खाता चुनें",
"noOtherAccounts": "कोई अन्य खाता नहीं मिला",
"kccbBankName": "कांगड़ा केंद्रीय सहकारी बैंक",
"fundTransferTitle": "धन हस्तांतरण",
"debitFromLabel": "से डेबिट करें",
"creditToLabel": "को क्रेडिट करें",
"remarksOptionalHint": "टिप्पणी (वैकल्पिक)",
"amountLabel": "राशि",
"amountRequiredError": "राशि आवश्यक है",
"validAmountError": "कृपया एक वैध राशि दर्ज करें",
"fetchingDailyLimitLoader": "दैनिक सीमा लाई जा रही है...",
"proceedButton": "आगे बढ़ें",
"enterKey": "दर्ज करें",
"backKey": "वापस",
"doneKey": "पूर्ण",
"transactionDate": "{date} को",
"paymentResultPng": "/payment_result.png",
"rubikFont": "Rubik",
"transactionDateLabel": "दिनांक: {date}",
"utrLabel": "UTR: {utr}",
"searchByNameOrAccountHint": "नाम या खाता संख्या से खोजें",
"savingsAccountDropdown": "बचत",
"beneficiaryExistsError": "लाभार्थी पहले से मौजूद है",
"somethingWentWrongShort": "कुछ गलत हो गया",
"currentAccountDropdown": "चालू",
"failedToDeleteBeneficiaryError": "लाभार्थी को हटाने में विफल: {error}",
"notAvailable": "उपलब्ध नहीं है",
"bankNameLabel": "बैंक का नाम",
"accountNumberLabel": "खाता संख्या",
"accountTypeLabel": "खाते का प्रकार",
"ifscCodeLabel": "IFSC कोड",
"branchNameLabel": "शाखा का नाम",
"enquiryEmailSubject": "पूछताछ",
"couldNotOpenEmailApp": "{email} के लिए ईमेल ऐप नहीं खोला जा सका",
"couldNotOpenDialer": "{phone} के लिए डायलर नहीं खोला जा सका",
"couldNotLaunchUrl": "{url} लॉन्च नहीं किया जा सका",
"complaintFormUrl": "https://kccbhp.bank.in/complaint-form/",
"complaintFormTitle": "शिकायत प्रपत्र",
"chairmanEmail": "chairman@kccb.in",
"chairmanPhone": "01892-222677",
"mdEmail": "md@kccb.in",
"mdPhone": "01892-224969",
"gmwEmail": "gmw@kccb.in",
"gmwPhone": "01892-223280",
"gmnEmail": "gmn@kccb.in",
"gmnPhone": "01892-224607",
"atmNameAddressHint": "नाम/पता",
"noMatchingAtms": "कोई मेल खाने वाला एटीएम नहीं मिला",
"faq1Question": "मैं मोबाइल बैंकिंग ऐप में कैसे लॉग इन करूं?",
"faq1Answer": "आप अपने ग्राहक नंबर और पासवर्ड का उपयोग करके लॉग इन कर सकते हैं। समर्थित उपकरणों के लिए बायोमेट्रिक लॉगिन (फिंगरप्रिंट) और एमपिन भी उ।",
"faq2Question": "क्या इस ऐप पर मेरी बैंकिंग जानकारी सुरक्षित है?",
"faq2Answer": "हां। हम आपके डेटा को सुरक्षित रखने के लिए उद्योग-मानक एन्क्रिप्शन और बहु-कारक प्रमाणीकरण का उपयोग करते हैं।",
"faq3Question": "मैं अपने खाते की शेष राशि कैसे देख सकता हूं?",
"faq3Answer": "लॉग इन करने के बाद, आपके खाते की शेष राशि होम स्क्रीन पर प्रदर्शित होगी। आप “खाते” अनुभाग के तहत विस्तृत शेष राशि भी देख सकते हैं।",
"faq4Question": "क्या मैं अन्य बैंक खातों में पैसे ट्रांसफर कर सकता हूं?",
"faq4Answer": "हां। आप भारत में किसी भी बैंक खाते में धनराशि स्थानांतरित करने के लिए एनईएफटी, आरटीजीएस या आईएमपीएस का उपयोग कर सकते हैं।",
"faq5Question": "मैं अपना लेनदेन इतिहास कैसे देखूं?",
"faq5Answer": "हाल के और पिछले लेनदेन देखने के लिए होम स्क्रीन के नीचे “खाता विवरण” आइकन पर क्लिक करें।",
"chequeEnquiryTitle": "चेक पूछताछ",
"chequeEnquirySubtitle": "आप अपनी जारी की गई चेक बुक, प्रस्तुत किए गए चेकों और रोके गए चेकों के विवरण देख सकते हैं, जिसमें प्रासंगिक तिथियां, चेक नं्य आवश्यक जानकारी शामिल है",
"stopChequeSubtitle": "अपनी जारी की गई चेकबुक से एक या अधिक चेकों के लिए स्टॉप आरंभ करें। यह आवश्यक सेवा अनधिकृत लेनदेन को रोकने और धोखाधड़ी से बचानद करती है।",
"chequeEnquiryFailedError": "चेक स्थिति लाने में विफल: {error}",
"accountNumberTitle": "खाता संख्या",
"noAccountsFound": "कोई खाता नहीं मिला",
"searchByChequeDetailsHint": "चेक विवरण द्वारा खोजें",
"noChequeStatusFound": "कोई चेक स्थिति नहीं मिली।",
"chequebookIssuedLabel": "चेकबुक जारी (CI)",
"branchCodeLabel": "शाखा कोड:",
"fromChequeLabel": "चेक से:",
"toChequeLabel": "चेक तक:",
"dateLabel": "दिनांक:",
"chequesCountLabel": "चेकों की संख्या:",
"presentedChequeLabel": "प्रस्तुत चेक (PR)",
"chequeNumberLabel": "चेक नंबर:",
"transactionCodeLabel": "लेनदेन कोड:",
"amountLabelWithColon": "राशि:",
"statusLabel": "स्थिति:",
"stopChequeLabel": "चेक रोकें (ST)",
"stopIssueDateLabel": "रोक जारी करने की तारीख:",
"stopExpiryDateLabel": "रोक समाप्ति तिथि:",
"emptyString": "",
"cashCreditAccountLabel": "नकद क्रेडिट खाता",
"stopChequeTitle": "चेक रोकें",
"noChequebookToStop": "से चेक रोकने के लिए कोई चेक बुक नहीं मिली।",
"stopSingleChequeButton": "एकल चेक रोकें",
"pleaseSelectAccountFirst": "कृपया पहले एक खाता चुनें।",
"stopMultipleChequesButton": "एकाधिक चेक रोकें",
"noChequeIssuedStatus": "कोई चेक जारी स्थिति नहीं मिली।",
"chequebookDetailsTitle": "चेकबुक विवरण",
"customerNameLabel": "ग्राहक का नाम:",
"cifNumberLabel": "CIF नंबर:",
"startingChequeNumberLabel": "प्रारंभिक चेक नंबर:",
"endingChequeNumberLabel": "अंतिम चेक नंबर:",
"issueDateLabel": "जारी करने की तारीख:",
"numberOfChequesLabel": "चेकों की संख्या:",
"instrumentTypeLabel": "उपकरण का प्रकार:",
"closeButton": "बंद करें",
"stopSingleChequeTitle": "एकल चेक रोकें",
"chequeNumberHint": "चेक नंबर *",
"pleaseEnterChequeNumberError": "कृपया एक चेक नंबर दर्ज करें",
"invalidChequeNumberFormatError": "अमान्य चेक नंबर प्रारूप",
"chequeNumberRangeError": "चेक नंबर {from} और {to} के बीच होना चाहिए",
"instrumentTypeHint": "उपकरण का प्रकार *",
"stopIssueDateHint": "रोक जारी करने की तारीख",
"stopExpiryDateHint": "रोक समाप्ति तिथि",
"stopAmountHint": "राशि रोकें",
"stopCommentHint": "टिप्पणी रोकें",
"chequebookIssueDateHint": "चेकबुक जारी करने की तारीख",
"successStatus": "सफलता",
"errorStatus": "त्रुटि",
"incorrectTpinErrorMessage": "आपके द्वारा दर्ज किया गया टीपिन गलत है। कृपया पुन: प्रयास करें।",
"internalServerError": "आंतरिक सर्वर त्रुटि",
"stopChequeButton": "चेक रोकें",
"stopMultipleChequesTitle": "एकाधिक चेक रोकें",
"fromChequeNumberHint": "चेक नंबर से *",
"toChequeNumberHint": "चेक नंबर तक *",
"failedToFetchChequeStatus": "चेक स्थिति लाने में विफल: {error}",
"revokeStopSubtitle": "अपने रोके गए चेकों को फिर से उपयोग करने के लिए निरस्त करें",
"positivePaySubtitle": "अनधिकृत उपयोग को रोकने के लिए अपने जारी किए गए चेक का विवरण जमा करें",
"positivePayTitle": "सकारात्मक वेतन",
"pleaseEnterAccountNumber": "कृपया खाता संख्या दर्ज करें",
"chequeNumber": "चेक संख्या",
"pleaseEnterChequeNumber": "कृपया चेक संख्या दर्ज करें",
"chequeIssuedDate": "चेक जारी करने की तारीख",
"pleaseSelectChequeIssuedDate": "कृपया चेक जारी करने की तारीख चुनें",
"payeeName": "प्राप्तकर्ता का नाम",
"pleaseEnterAmount": "कृपया राशि दर्ज करें",
"processingData": "डेटा संसाधित हो रहा है",
"revokeStopCheque": "चेक रोको रद्द करें",
"currentAccount": "चालू खाता",
"cashCreditAccount": "नगद श्रेय खाता",
"revokeSingleStop": "एकल रोक रद्द करें",
"noStoppedChequesPresent": "कोई रोका हुआ चेक मौजूद नहीं है",
"revokeMultipleStops": "कई रोक रद्द करें",
"revokeSingleStopTitle": "एकल रोक रद्द करें",
"chequeFound": "चेक मिला",
"chequeFixed": "चेक ठीक किया गया",
"other": "अन्य",
"revokeIssueDate": "रोक जारी करने की तारीख रद्द करें",
"revokeExpiryDate": "रोक समाप्ति तिथि रद्द करें",
"revokeAmount": "राशि रद्द करें",
"revokeComment": "टिप्पणी रद्द करें",
"otherReasons": "अन्य कारण :",
"revokeStopButton": "रोक रद्द करें",
"invalidTpin": "अमान्य टीपिन",
"incorrectTpinMessage": "आपके द्वारा दर्ज किया गया टीपिन गलत है। कृपया पुनः प्रयास करें।",
"chequeAlreadyStopped": "चुना हुआ चेक पहले से ही रोका हुआ है",
"chequeAlreadyPresented": "चुना हुआ चेक पहले से ही प्रस्तुत किया जा चुका है",
"chequeLost": "चेक खो गया",
"chequeStolen": "चेक चोरी हो गया",
"chequeMissing": "चेक गायब है",
"chequeDamaged": "चेक क्षतिग्रस्त है",
"close": "बंद करें",
"governmentSchemes": "सरकारी योजनाएं",
"pradhanMantriYojana": "प्रधानमंत्री योजना",
"atalPensionYojana": "अटल पेंशन योजना",
"registerForAtalPensionYojana": "अटल पेंशन योजना के लिए पंजीकरण करें",
"secureYourFutureAPY": "अटल पेंशन योजना (APY) सेवानिवृत्ति योजना के साथ अपना भविष्य सुरक्षित करें। कुछ ही चरणों में पंजीकरण करें।",
"enrollPMJJBYPMSBY": "प्रधानमंत्री जीवन ज्योति बीमा योजना (PMJJBY) और प्रधानमंत्री सुरक्षा बीमा योजना (PMSBY) बीमा योजनाओं में नामांकन करें",
"apyRegistration": "APY पंजीकरण",
"apyDescription": "अटल पेंशन योजना (APY) भारत के नागरिकों के लिए एक आवधिक योगदान-आधारित पेंशन योजना है।",
"pmjjbyPmsbyDescription": "अपनी प्रधानमंत्री जीवन ज्योति बीमा योजना (PMJJBY) और प्रधानमंत्री सुरक्षा बीमा योजना (PMSBY) योजनाओं के बारे में बनाएं और पूछताछ करें।",
"selectScheme": "योजना चुनें",
"pmjjbyFull": "प्रधानमंत्री जीवन ज्योति बीमा योजना (PMJJBY)",
"pmsbyFull": "प्रधानमंत्री सुरक्षा बीमा योजना (PMSBY)",
"create": "बनाएं",
"pleaseSelectAccountNumber": "कृपया खाता संख्या चुनें",
"pleaseSelectSchemeFirst": "कृपया पहले एक योजना चुनें",
"fetchingDetails": "विवरण प्राप्त किया जा रहा है...",
"failedToFetchDetails": "विवरण प्राप्त करने में विफल या कोई डेटा नहीं मिला।",
"pmjjbyDetails": "PMJJBY विवरण",
"pmsbyDetails": "PMSBY विवरण",
"cifNumber": "CIF संख्या",
"selectFinancialYear": "वित्तीय वर्ष चुनें",
"schemeDetails": "योजना विवरण",
"customerName": "ग्राहक का नाम",
"policyNumber": "पॉलिसी संख्या",
"premiumAmount": "प्रीमियम राशि",
"nomineeName": "नामांकित व्यक्ति का नाम",
"journalNo": "जर्नल नंबर",
"noRecordFound": "कोई रिकॉर्ड नहीं मिला",
"noDataFoundYear": "चयनित वर्ष के लिए कोई डेटा नहीं मिला।",
"pmjjbyRegistration": "PMJJBY पंजीकरण",
"pmsbyRegistration": "PMSBY पंजीकरण",
"customerNo": "ग्राहक संख्या",
"aadhaarNo": "आधार संख्या",
"customerDobFormat": "ग्राहक की जन्म तिथि (DD/MM/YYYY)",
"gender": "लिंग",
"marriedYesNo": "विवाहित (हाँ/नहीं)",
"pan": "पैन",
"dateOfAcctOpening": "खाता खोलने की तिथि",
"pincode": "पिनकोड",
"state": "राज्य",
"country": "देश",
"ruralCategory": "ग्रामीण श्रेणी",
"policyDetails": "पॉलिसी विवरण",
"financialYear": "वित्तीय वर्ष",
"healthStatus": "स्वास्थ्य की स्थिति",
"collectionChannel": "संग्रह चैनल",
"nomineeDetails": "नामांकित व्यक्ति का विवरण",
"nomineeAddress": "नामांकित व्यक्ति का पता",
"nomineeRelationship": "नामांकित व्यक्ति से संबंध",
"nomineeMinor": "नामांकित व्यक्ति नाबालिग है",
"response": "प्रतिक्रिया",
"recordAlreadyExists": "इस अनुरोध के लिए एक रिकॉर्ड पहले से मौजूद है। आपका सबमिशन पहले पंजीकृत किया जा चुका है।",
"address1": "पता 1",
"address2": "पता 2",
"city": "शहर",
"relationWithNominee": "नामांकित व्यक्ति के साथ संबंध",
"policyStatus": "पॉलिसी की स्थिति"
} }

View File

@@ -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());
} }

View File

@@ -773,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:
@@ -861,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
@@ -1003,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

View File

@@ -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,6 +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
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"