Compare commits
9 Commits
1c3a07bd66
...
sms-testin
| Author | SHA1 | Date | |
|---|---|---|---|
| 6861a4a349 | |||
| 298c0c199f | |||
| dfdc293309 | |||
| 1da7574ddb | |||
| 30a45015d0 | |||
| d8ebd0ed0e | |||
| 89569ab1c3 | |||
| 5c959ba15c | |||
| d89a4f5109 |
@@ -126,4 +126,27 @@ class BeneficiaryService {
|
||||
throw Exception('Unexpected error: ${e.toString()}');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response> updateLimit({
|
||||
required String beneficiaryAccountNo,
|
||||
required String newLimit,
|
||||
}) async {
|
||||
log('inside update limit of beneficiary service');
|
||||
final response = await _dio.patch(
|
||||
'/api/beneficiary/update-limit',
|
||||
data: {
|
||||
'beneficiaryAccountNo': beneficiaryAccountNo,
|
||||
'newLimit': int.tryParse(newLimit),
|
||||
},
|
||||
options: Options(
|
||||
sendTimeout: const Duration(seconds: 60),
|
||||
receiveTimeout: const Duration(seconds: 60),
|
||||
),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception("INTERNAL SERVER ERROR");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
class Cheque {
|
||||
@@ -130,9 +128,64 @@ class ChequeService {
|
||||
'tpin': tpin,
|
||||
},
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception(jsonEncode(response.data));
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
213
lib/api/services/yojna_service.dart
Normal file
213
lib/api/services/yojna_service.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ class Beneficiary {
|
||||
final String ifscCode;
|
||||
final String? bankName;
|
||||
final String? branchName;
|
||||
final String? transactionLimit;
|
||||
final String? tpin;
|
||||
|
||||
Beneficiary({
|
||||
@@ -16,6 +17,7 @@ class Beneficiary {
|
||||
required this.ifscCode,
|
||||
this.bankName,
|
||||
this.branchName,
|
||||
this.transactionLimit,
|
||||
this.tpin,
|
||||
});
|
||||
|
||||
@@ -30,6 +32,7 @@ class Beneficiary {
|
||||
ifscCode: json['ifsc_code'] ?? json['ifscCode'] ?? '',
|
||||
bankName: json['bank_name'] ?? json['bankName'] ?? '',
|
||||
branchName: json['branch_name'] ?? json['branchName'] ?? '',
|
||||
transactionLimit: json['transactionLimit'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:dio/dio.dart';
|
||||
import 'package:kmobile/api/services/beneficiary_service.dart';
|
||||
import 'package:kmobile/api/services/payment_service.dart';
|
||||
import 'package:kmobile/api/services/user_service.dart';
|
||||
import 'package:kmobile/api/services/yojna_service.dart';
|
||||
import 'package:kmobile/data/repositories/transaction_repository.dart';
|
||||
import 'package:kmobile/features/auth/controllers/theme_cubit.dart';
|
||||
import 'package:kmobile/features/auth/controllers/theme_mode_cubit.dart';
|
||||
@@ -58,6 +59,7 @@ Future<void> setupDependencies() async {
|
||||
getIt.registerSingleton<ImpsService>(ImpsService(getIt<Dio>()));
|
||||
getIt.registerSingleton<BranchService>(BranchService(getIt<Dio>()));
|
||||
getIt.registerSingleton<ChequeService>(ChequeService(getIt<Dio>()));
|
||||
getIt.registerSingleton<YojnaService>(YojnaService(getIt<Dio>()));
|
||||
getIt.registerLazySingleton<ChangePasswordService>(
|
||||
() => ChangePasswordService(getIt<Dio>()),
|
||||
);
|
||||
|
||||
175
lib/features/account_opening/screens/account_opening_screen.dart
Normal file
175
lib/features/account_opening/screens/account_opening_screen.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
244
lib/features/account_opening/screens/fd_screen.dart
Normal file
244
lib/features/account_opening/screens/fd_screen.dart
Normal file
@@ -0,0 +1,244 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:kmobile/features/account_opening/screens/interest_rates_screen.dart';
|
||||
|
||||
class FdScreen extends StatefulWidget {
|
||||
const FdScreen({super.key});
|
||||
|
||||
@override
|
||||
State<FdScreen> createState() => _FdScreenState();
|
||||
}
|
||||
|
||||
class _FdScreenState extends State<FdScreen> {
|
||||
final TextEditingController _debitAccountController = TextEditingController();
|
||||
final TextEditingController _amountController = TextEditingController();
|
||||
final TextEditingController _yearsController = TextEditingController();
|
||||
final TextEditingController _monthsController = TextEditingController();
|
||||
final TextEditingController _daysController = TextEditingController();
|
||||
|
||||
String _rateOfInterest = "N/A";
|
||||
String _maturityDate = "N/A";
|
||||
String _maturityAmount = "N/A";
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_debitAccountController.dispose();
|
||||
_amountController.dispose();
|
||||
_yearsController.dispose();
|
||||
_monthsController.dispose();
|
||||
_daysController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Fixed Deposit (FD)'),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Explanation Tile
|
||||
Card(
|
||||
margin: const EdgeInsets.only(bottom: 20),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Earn more on your savings with a simple, secure Fixed Deposit.',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Debit Account Number
|
||||
TextFormField(
|
||||
controller: _debitAccountController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Debit Account Number',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Enter Amount
|
||||
TextFormField(
|
||||
controller: _amountController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Enter Amount',
|
||||
border: OutlineInputBorder(),
|
||||
prefixText: '₹ '
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Duration and Interest Rates Link
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Duration',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const InterestRatesScreen()),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
'Interest Rates',
|
||||
style: Theme.of(context).textTheme.titleSmall
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
// Duration TextBoxes
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _yearsController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Years',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _monthsController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Months',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _daysController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Days',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Rate of Interest and Maturity Date Display
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Rate of Interest',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
_rateOfInterest,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Maturity Date',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
_maturityDate,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// Maturity Amount Display
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Maturity Amount',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
_maturityAmount,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
|
||||
// Proceed Button
|
||||
Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
// TODO: Implement proceed logic
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 15),
|
||||
),
|
||||
child: const Text(
|
||||
'Proceed',
|
||||
style: TextStyle(fontSize: 18),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class InterestRatesScreen extends StatelessWidget {
|
||||
const InterestRatesScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Interest Rates'),
|
||||
),
|
||||
body: const Center(
|
||||
child: Text('This page will display a table of interest rates.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
19
lib/features/account_opening/screens/loan_screen.dart
Normal file
19
lib/features/account_opening/screens/loan_screen.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LoanScreen extends StatelessWidget {
|
||||
const LoanScreen({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Request Loan"),
|
||||
),
|
||||
body: const Center(
|
||||
child: Text("Loan Account"),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
19
lib/features/account_opening/screens/rd_screen.dart
Normal file
19
lib/features/account_opening/screens/rd_screen.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RecurringDepositScreen extends StatelessWidget {
|
||||
const RecurringDepositScreen({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Recurring Deposit"),
|
||||
),
|
||||
body: const Center(
|
||||
child: Text("Recurring Deposit"),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
19
lib/features/account_opening/screens/td_screen.dart
Normal file
19
lib/features/account_opening/screens/td_screen.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TermDepositScreen extends StatelessWidget {
|
||||
const TermDepositScreen({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Term Deposit (TD)"),
|
||||
),
|
||||
body: const Center(
|
||||
child: Text("Term Deposit (TD)"),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -103,7 +103,12 @@ class _VerificationScreenState extends State<VerificationScreen> {
|
||||
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;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:kmobile/data/models/beneficiary.dart';
|
||||
import 'package:kmobile/di/injection.dart';
|
||||
import 'package:kmobile/widgets/bank_logos.dart';
|
||||
@@ -6,16 +7,39 @@ import 'package:kmobile/api/services/beneficiary_service.dart';
|
||||
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
|
||||
class BeneficiaryDetailsScreen extends StatelessWidget {
|
||||
class BeneficiaryDetailsScreen extends StatefulWidget {
|
||||
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>();
|
||||
late String _currentLimit;
|
||||
final _limitController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_currentLimit = (widget.beneficiary.transactionLimit == null ||
|
||||
widget.beneficiary.transactionLimit!.isEmpty)
|
||||
? 'N/A'
|
||||
: widget.beneficiary.transactionLimit!;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_limitController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _deleteBeneficiary(BuildContext context) async {
|
||||
try {
|
||||
await service.deleteBeneficiary(beneficiary.accountNo);
|
||||
await service.deleteBeneficiary(widget.beneficiary.accountNo);
|
||||
if (!context.mounted) {
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -96,11 +187,11 @@ class BeneficiaryDetailsScreen extends StatelessWidget {
|
||||
CircleAvatar(
|
||||
radius: 24,
|
||||
backgroundColor: Colors.transparent,
|
||||
child: getBankLogo(beneficiary.bankName, context),
|
||||
child: getBankLogo(widget.beneficiary.bankName, context),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
beneficiary.name,
|
||||
widget.beneficiary.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
@@ -108,28 +199,28 @@ class BeneficiaryDetailsScreen extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
_buildDetailRow('${AppLocalizations.of(context).bankName} ',
|
||||
beneficiary.bankName ?? 'N/A'),
|
||||
widget.beneficiary.bankName ?? 'N/A'),
|
||||
_buildDetailRow(
|
||||
'${AppLocalizations.of(context).accountNumber} ',
|
||||
beneficiary.accountNo),
|
||||
widget.beneficiary.accountNo),
|
||||
_buildDetailRow(
|
||||
'${AppLocalizations.of(context).accountType} ',
|
||||
beneficiary.accountType),
|
||||
widget.beneficiary.accountType),
|
||||
_buildDetailRow('${AppLocalizations.of(context).ifscCode} ',
|
||||
beneficiary.ifscCode),
|
||||
widget.beneficiary.ifscCode),
|
||||
_buildDetailRow('${AppLocalizations.of(context).branchName} ',
|
||||
beneficiary.branchName ?? 'N/A'),
|
||||
widget.beneficiary.branchName ?? 'N/A'),
|
||||
_buildDetailRow(
|
||||
"Beneficiary Transactional Limit", _currentLimit),
|
||||
const Spacer(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
// ElevatedButton.icon(
|
||||
// onPressed: () {
|
||||
// // Set Transaction Limit for this beneficiary
|
||||
// },
|
||||
// icon: const Icon(Icons.currency_rupee),
|
||||
// label: const Text('Set Limit'),
|
||||
// ),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _showEditLimitDialog,
|
||||
icon: const Icon(Icons.currency_rupee),
|
||||
label: Text(AppLocalizations.of(context).editLimit),
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
// Delete beneficiary option
|
||||
@@ -183,3 +274,4 @@ class BeneficiaryDetailsScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,12 +90,12 @@ class CardTile extends StatelessWidget {
|
||||
const Text(
|
||||
"Kangra Central Co-operative Bank",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 15.5,
|
||||
color: Color.fromARGB(255, 238, 237, 237),
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.visible,
|
||||
maxLines: 2,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -19,7 +19,6 @@ class _CardManagementScreen extends State<CardManagementScreen> {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
title: Text(
|
||||
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),
|
||||
CardManagementTile(
|
||||
|
||||
@@ -45,7 +45,7 @@ class _CardPinChangeDetailsScreen extends State<CardPinChangeDetailsScreen> {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
AppLocalizations.of(context).cardDetails,
|
||||
AppLocalizations.of(context).changeCardPin,
|
||||
),
|
||||
centerTitle: false,
|
||||
),
|
||||
@@ -66,12 +66,8 @@ class _CardPinChangeDetailsScreen extends State<CardPinChangeDetailsScreen> {
|
||||
isDense: true,
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black, width: 2),
|
||||
),
|
||||
enabledBorder: const OutlineInputBorder(),
|
||||
focusedBorder: const OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
textInputAction: TextInputAction.next,
|
||||
@@ -92,13 +88,8 @@ class _CardPinChangeDetailsScreen extends State<CardPinChangeDetailsScreen> {
|
||||
filled: true,
|
||||
fillColor:
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide:
|
||||
BorderSide(color: Colors.black, width: 2),
|
||||
),
|
||||
enabledBorder: const OutlineInputBorder(),
|
||||
focusedBorder: const OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
textInputAction: TextInputAction.next,
|
||||
@@ -123,13 +114,8 @@ class _CardPinChangeDetailsScreen extends State<CardPinChangeDetailsScreen> {
|
||||
filled: true,
|
||||
fillColor:
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide:
|
||||
BorderSide(color: Colors.black, width: 2),
|
||||
),
|
||||
enabledBorder: const OutlineInputBorder(),
|
||||
focusedBorder: const OutlineInputBorder(),
|
||||
),
|
||||
validator: (value) => value != null &&
|
||||
value.isNotEmpty
|
||||
@@ -149,12 +135,8 @@ class _CardPinChangeDetailsScreen extends State<CardPinChangeDetailsScreen> {
|
||||
isDense: true,
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black, width: 2),
|
||||
),
|
||||
enabledBorder: const OutlineInputBorder(),
|
||||
focusedBorder: const OutlineInputBorder(),
|
||||
),
|
||||
textInputAction: TextInputAction.done,
|
||||
keyboardType: TextInputType.phone,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:kmobile/data/models/user.dart';
|
||||
import 'package:kmobile/features/cheque/screens/cheque_enquiry_screen.dart';
|
||||
import 'package:kmobile/features/cheque/screens/positive_pay_screen.dart';
|
||||
import 'package:kmobile/features/cheque/screens/revoke_stop_screen.dart';
|
||||
import 'package:kmobile/features/cheque/screens/stop_cheque_screen.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
@@ -42,8 +44,6 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
|
||||
child: ChequeManagementCardTile(
|
||||
icon: Symbols.payments,
|
||||
label: AppLocalizations.of(context).chequeEnquiryTitle,
|
||||
subtitle:
|
||||
AppLocalizations.of(context).chequeEnquirySubtitle,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
@@ -61,7 +61,6 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
|
||||
child: ChequeManagementCardTile(
|
||||
icon: Symbols.block_sharp,
|
||||
label: AppLocalizations.of(context).stopCheque,
|
||||
subtitle: AppLocalizations.of(context).stopChequeSubtitle,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
@@ -75,6 +74,40 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ChequeManagementCardTile(
|
||||
icon: Symbols.stop_circle,
|
||||
label: AppLocalizations.of(context).revokeStop,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => RevokeStopChequeScreen(
|
||||
users: users,
|
||||
selectedIndex: selectedAccountIndex,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ChequeManagementCardTile(
|
||||
icon: Symbols.check_circle, // Using check_circle for Positive Pay
|
||||
label: AppLocalizations.of(context).positivePayTitle,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PositivePayScreen(
|
||||
users: users,
|
||||
selectedIndex: selectedAccountIndex,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -101,7 +134,6 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
|
||||
class ChequeManagementCardTile extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final String? subtitle;
|
||||
final VoidCallback onTap;
|
||||
final bool disable;
|
||||
|
||||
@@ -109,7 +141,6 @@ class ChequeManagementCardTile extends StatelessWidget {
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.label,
|
||||
this.subtitle,
|
||||
required this.onTap,
|
||||
this.disable = false,
|
||||
});
|
||||
@@ -152,19 +183,6 @@ class ChequeManagementCardTile extends StatelessWidget {
|
||||
: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
if (subtitle != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
subtitle!,
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: disable
|
||||
? theme.disabledColor
|
||||
: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -173,4 +191,4 @@ class ChequeManagementCardTile extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
334
lib/features/cheque/screens/positive_pay_screen.dart
Normal file
334
lib/features/cheque/screens/positive_pay_screen.dart
Normal 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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
338
lib/features/cheque/screens/revoke _stop_multiple_screen.dart
Normal file
338
lib/features/cheque/screens/revoke _stop_multiple_screen.dart
Normal 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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
361
lib/features/cheque/screens/revoke_stop_screen.dart
Normal file
361
lib/features/cheque/screens/revoke_stop_screen.dart
Normal 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 ?? ''),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
304
lib/features/cheque/screens/revoke_stop_single_screen.dart
Normal file
304
lib/features/cheque/screens/revoke_stop_single_screen.dart
Normal 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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -34,9 +34,19 @@ class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
|
||||
final _stopIssueDateController = TextEditingController();
|
||||
final _stopExpiryDateController = TextEditingController();
|
||||
final _stopAmountController = TextEditingController();
|
||||
final _stopCommentController = TextEditingController();
|
||||
final _chequeService = getIt<ChequeService>();
|
||||
|
||||
String? _selectedComment;
|
||||
final _otherCommentController = TextEditingController();
|
||||
bool _showOtherCommentField = false;
|
||||
final List<String> _commentOptions = [
|
||||
'Cheque Lost',
|
||||
'Cheque Stolen',
|
||||
'Cheque Missing',
|
||||
'Cheque Damaged',
|
||||
'Other'
|
||||
];
|
||||
|
||||
String _formatDate(String dateString) {
|
||||
if (dateString.length != 8) {
|
||||
return dateString; // Return as is if not in expected ddmmyyyy format
|
||||
@@ -51,6 +61,21 @@ class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _selectDate(TextEditingController controller) async {
|
||||
final DateTime? picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime.now(),
|
||||
lastDate: DateTime(2101),
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
controller.text =
|
||||
'${picked.day.toString().padLeft(2, '0')}/${picked.month.toString().padLeft(2, '0')}/${picked.year}';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showResponseDialog(String title, String message) async {
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
@@ -110,6 +135,7 @@ class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).fromChequeNumberHint,
|
||||
border: const OutlineInputBorder(),
|
||||
errorMaxLines: 2,
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (value) {
|
||||
@@ -139,6 +165,7 @@ class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).toChequeNumberHint,
|
||||
border: const OutlineInputBorder(),
|
||||
errorMaxLines: 2,
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (value) {
|
||||
@@ -174,18 +201,30 @@ class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _stopIssueDateController,
|
||||
readOnly: true,
|
||||
onTap: () => _selectDate(_stopIssueDateController),
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).stopIssueDateHint,
|
||||
border: const OutlineInputBorder(),
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.calendar_today),
|
||||
onPressed: () => _selectDate(_stopIssueDateController),
|
||||
),
|
||||
),
|
||||
keyboardType: TextInputType.datetime,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _stopExpiryDateController,
|
||||
readOnly: true,
|
||||
onTap: () => _selectDate(_stopExpiryDateController),
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).stopExpiryDateHint,
|
||||
border: const OutlineInputBorder(),
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.calendar_today),
|
||||
onPressed: () => _selectDate(_stopExpiryDateController),
|
||||
),
|
||||
),
|
||||
keyboardType: TextInputType.datetime,
|
||||
),
|
||||
@@ -199,13 +238,39 @@ class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _stopCommentController,
|
||||
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),
|
||||
@@ -236,23 +301,34 @@ class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
|
||||
stopIssueDate: _stopIssueDateController.text,
|
||||
stopExpiryDate: _stopExpiryDateController.text,
|
||||
stopAmount: _stopAmountController.text,
|
||||
stopComment: _stopCommentController.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);
|
||||
} else {
|
||||
_showResponseDialog('Error', message);
|
||||
} if (status == 'ERROR') {
|
||||
String errMessage = "error";
|
||||
if(code == '0429') {
|
||||
errMessage = 'The selected Cheque is already stopped';
|
||||
} else if(code == '0748') {
|
||||
errMessage = 'The selected Cheque is already presented';
|
||||
}
|
||||
_showResponseDialog('Error', errMessage);
|
||||
}
|
||||
if(responseString.contains('INCORRECT_TPIN')){
|
||||
_showResponseDialog('Invalid TPIN',
|
||||
'The TPIN you entered is incorrect. Please try again.');
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
print('inside catch block');
|
||||
print(e.toString());
|
||||
|
||||
try {
|
||||
final errorBodyString =
|
||||
e.toString().split('Exception: ')[1];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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';
|
||||
@@ -31,9 +32,19 @@ class _StopSingleChequeScreenState extends State<StopSingleChequeScreen> {
|
||||
final _stopIssueDateController = TextEditingController();
|
||||
final _stopExpiryDateController = TextEditingController();
|
||||
final _stopAmountController = TextEditingController();
|
||||
final _stopCommentController = TextEditingController();
|
||||
final _chequeService = getIt<ChequeService>();
|
||||
|
||||
String? _selectedComment;
|
||||
final _otherCommentController = TextEditingController();
|
||||
bool _showOtherCommentField = false;
|
||||
final List<String> _commentOptions = [
|
||||
'Cheque Lost',
|
||||
'Cheque Stolen',
|
||||
'Cheque Missing',
|
||||
'Cheque Damaged',
|
||||
'Other'
|
||||
];
|
||||
|
||||
String _formatDate(String dateString) {
|
||||
if (dateString.length != 8) {
|
||||
return dateString; // Return as is if not in expected ddmmyyyy format
|
||||
@@ -48,6 +59,21 @@ class _StopSingleChequeScreenState extends State<StopSingleChequeScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _selectDate(TextEditingController controller) async {
|
||||
final DateTime? picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime.now(),
|
||||
lastDate: DateTime(2101),
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
controller.text =
|
||||
'${picked.day.toString().padLeft(2, '0')}/${picked.month.toString().padLeft(2, '0')}/${picked.year}';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showResponseDialog(String title, String message) async {
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
@@ -107,6 +133,7 @@ class _StopSingleChequeScreenState extends State<StopSingleChequeScreen> {
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).chequeNumberLabel,
|
||||
border: OutlineInputBorder(),
|
||||
errorMaxLines: 2,
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (value) {
|
||||
@@ -142,18 +169,30 @@ class _StopSingleChequeScreenState extends State<StopSingleChequeScreen> {
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _stopIssueDateController,
|
||||
readOnly: true,
|
||||
onTap: () => _selectDate(_stopIssueDateController),
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).stopIssueDateLabel,
|
||||
border: const OutlineInputBorder(),
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.calendar_today),
|
||||
onPressed: () => _selectDate(_stopIssueDateController),
|
||||
),
|
||||
),
|
||||
keyboardType: TextInputType.datetime,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _stopExpiryDateController,
|
||||
readOnly: true,
|
||||
onTap: () => _selectDate(_stopExpiryDateController),
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).stopExpiryDateLabel,
|
||||
border: const OutlineInputBorder(),
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.calendar_today),
|
||||
onPressed: () => _selectDate(_stopExpiryDateController),
|
||||
),
|
||||
),
|
||||
keyboardType: TextInputType.datetime,
|
||||
),
|
||||
@@ -167,13 +206,39 @@ class _StopSingleChequeScreenState extends State<StopSingleChequeScreen> {
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _stopCommentController,
|
||||
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),
|
||||
@@ -205,28 +270,39 @@ class _StopSingleChequeScreenState extends State<StopSingleChequeScreen> {
|
||||
stopIssueDate: _stopIssueDateController.text,
|
||||
stopExpiryDate: _stopExpiryDateController.text,
|
||||
stopAmount: _stopAmountController.text,
|
||||
stopComment: _stopCommentController.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);
|
||||
} else {
|
||||
_showResponseDialog('Error', message);
|
||||
} if (status == 'ERROR') {
|
||||
String errMessage = "error";
|
||||
if(code == '0429') {
|
||||
errMessage = 'The selected Cheque is already stopped';
|
||||
} else if(code == '0748') {
|
||||
errMessage = 'The selected Cheque is already presented';
|
||||
}
|
||||
_showResponseDialog('Error', errMessage);
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
print('inside catch block');
|
||||
print(e.toString());
|
||||
|
||||
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') &&
|
||||
final errorBody = jsonDecode(errorBodyString);
|
||||
if (errorBody.containsKey('error') &&
|
||||
errorBody['error'] == 'INCORRECT_TPIN') {
|
||||
_showResponseDialog('Invalid TPIN',
|
||||
'The TPIN you entered is incorrect. Please try again.');
|
||||
|
||||
@@ -11,11 +11,11 @@ import 'package:kmobile/features/auth/controllers/auth_state.dart';
|
||||
import 'package:kmobile/features/cheque/screens/cheque_management_screen.dart';
|
||||
import 'package:kmobile/features/customer_info/screens/customer_info_screen.dart';
|
||||
import 'package:kmobile/features/beneficiaries/screens/manage_beneficiaries_screen.dart';
|
||||
import 'package:kmobile/features/enquiry/screens/enquiry_screen.dart';
|
||||
import 'package:kmobile/features/fund_transfer/screens/fund_transfer_screen.dart';
|
||||
import 'package:kmobile/features/profile/profile_screen.dart';
|
||||
import 'package:kmobile/features/quick_pay/screens/quick_pay_screen.dart';
|
||||
import 'package:kmobile/features/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:local_auth/local_auth.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
@@ -636,13 +636,14 @@ class _DashboardScreenState extends State<DashboardScreen>
|
||||
ManageBeneficiariesScreen(
|
||||
customerName: currAccount.name!)));
|
||||
}, disable: false),
|
||||
_buildQuickLink(Symbols.support_agent,
|
||||
AppLocalizations.of(context).contactUs, () {
|
||||
_buildQuickLink(Symbols.family_group,
|
||||
AppLocalizations.of(context).governmentSchemes, () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
const EnquiryScreen()));
|
||||
GovSchemeScreen(users: users,
|
||||
selectedIndex: selectedAccountIndex)));
|
||||
}),
|
||||
_buildQuickLink(
|
||||
Symbols.checkbook,
|
||||
|
||||
@@ -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/enquiry_screen.dart';
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
@@ -43,7 +46,6 @@ class _ServiceScreen extends State<ServiceScreen> {
|
||||
disabled: false,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: ServiceManagementTile(
|
||||
icon: Symbols.question_mark,
|
||||
@@ -57,7 +59,6 @@ class _ServiceScreen extends State<ServiceScreen> {
|
||||
disabled: false,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: ServiceManagementTile(
|
||||
icon: Symbols.location_pin,
|
||||
@@ -71,7 +72,46 @@ class _ServiceScreen extends State<ServiceScreen> {
|
||||
disabled: false,
|
||||
),
|
||||
),
|
||||
// No Spacer() needed here as Expanded children will fill space
|
||||
// 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
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -122,32 +162,33 @@ class ServiceManagementTile extends StatelessWidget {
|
||||
onTap:
|
||||
disabled ? 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: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 48, // Make icon larger
|
||||
color:
|
||||
disabled ? theme.disabledColor : theme.colorScheme.primary,
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 48, // Make icon larger
|
||||
color:
|
||||
disabled ? theme.disabledColor : theme.colorScheme.primary,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
label,
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: disabled
|
||||
? theme.disabledColor
|
||||
: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
label,
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: disabled
|
||||
? theme.disabledColor
|
||||
: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
113
lib/features/yojna/screens/apy_screen.dart
Normal file
113
lib/features/yojna/screens/apy_screen.dart
Normal 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;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
165
lib/features/yojna/screens/gov_scheme_screen.dart
Normal file
165
lib/features/yojna/screens/gov_scheme_screen.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
339
lib/features/yojna/screens/pm_main_screen.dart
Normal file
339
lib/features/yojna/screens/pm_main_screen.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
218
lib/features/yojna/screens/pmjjby_enquiry_screen.dart
Normal file
218
lib/features/yojna/screens/pmjjby_enquiry_screen.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
333
lib/features/yojna/screens/pmjjby_screen.dart
Normal file
333
lib/features/yojna/screens/pmjjby_screen.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
218
lib/features/yojna/screens/pmsby_enquiry_screen.dart
Normal file
218
lib/features/yojna/screens/pmsby_enquiry_screen.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
350
lib/features/yojna/screens/pmsby_screen.dart
Normal file
350
lib/features/yojna/screens/pmsby_screen.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -555,5 +555,100 @@
|
||||
"stopChequeButton": "Stop Cheque",
|
||||
"stopMultipleChequesTitle": "Stop Multiple Cheques",
|
||||
"fromChequeNumberHint": "From Cheque Number *",
|
||||
"toChequeNumberHint": "To 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"
|
||||
}
|
||||
@@ -556,5 +556,99 @@
|
||||
"stopChequeButton": "चेक रोकें",
|
||||
"stopMultipleChequesTitle": "एकाधिक चेक रोकें",
|
||||
"fromChequeNumberHint": "चेक नंबर से *",
|
||||
"toChequeNumberHint": "चेक नंबर तक *"
|
||||
}
|
||||
"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": "पॉलिसी की स्थिति"
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@ void main() async {
|
||||
]);
|
||||
|
||||
// Check for device compromise
|
||||
// final compromisedMessage = await SecurityService.deviceCompromisedMessage;
|
||||
// if (compromisedMessage != null) {
|
||||
// runApp(MaterialApp(
|
||||
// home: SecurityErrorScreen(message: compromisedMessage),
|
||||
// ));
|
||||
// return;
|
||||
// }
|
||||
final compromisedMessage = await SecurityService.deviceCompromisedMessage;
|
||||
if (compromisedMessage != null) {
|
||||
runApp(MaterialApp(
|
||||
home: SecurityErrorScreen(message: compromisedMessage),
|
||||
));
|
||||
return;
|
||||
}
|
||||
await setupDependencies();
|
||||
runApp(const KMobile());
|
||||
}
|
||||
|
||||
64
pubspec.lock
64
pubspec.lock
@@ -69,10 +69,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.4.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -93,18 +93,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.19.1"
|
||||
confetti:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -189,10 +189,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
version: "1.3.3"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -425,10 +425,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.0"
|
||||
version: "0.20.2"
|
||||
jailbreak_root_detection:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -457,26 +457,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.5"
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -537,10 +537,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.16+1"
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -561,10 +561,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
version: "1.16.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -609,10 +609,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.0"
|
||||
version: "1.9.1"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -881,7 +881,7 @@ packages:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
version: "0.0.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -902,18 +902,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -934,10 +934,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
version: "0.7.6"
|
||||
timezone:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1054,10 +1054,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1115,5 +1115,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.5.0 <4.0.0"
|
||||
dart: ">=3.8.0-0 <4.0.0"
|
||||
flutter: ">=3.24.0"
|
||||
|
||||
Reference in New Issue
Block a user