Compare commits
8 Commits
new-ui
...
sms-testin
| Author | SHA1 | Date | |
|---|---|---|---|
| 89569ab1c3 | |||
| 5c959ba15c | |||
| d89a4f5109 | |||
| 1c3a07bd66 | |||
| d44ee5590e | |||
| 715162b713 | |||
| 8149ef2a5b | |||
| 1a2dea611b |
@@ -2,6 +2,8 @@
|
|||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
|
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
|
||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.SEND_SMS"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
|
||||||
<application
|
<application
|
||||||
android:label="kmobile"
|
android:label="kmobile"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
|||||||
@@ -8,6 +8,41 @@ class AuthService {
|
|||||||
final Dio _dio;
|
final Dio _dio;
|
||||||
AuthService(this._dio);
|
AuthService(this._dio);
|
||||||
|
|
||||||
|
Future<void> simVerify(String uuid, String cifNo) async {
|
||||||
|
try {
|
||||||
|
final response = await _dio.post('/api/sim-details-verify', data: {
|
||||||
|
'uuid': uuid,
|
||||||
|
'cifNo': cifNo,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final String message = response.data.toString().toUpperCase();
|
||||||
|
|
||||||
|
if (message == "VERIFIED") {
|
||||||
|
return; // Success
|
||||||
|
} else {
|
||||||
|
throw AuthException(message); // Throw message received
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw AuthException('Verification Failed');
|
||||||
|
}
|
||||||
|
} on DioException catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.response?.statusCode == 401) {
|
||||||
|
throw AuthException(
|
||||||
|
e.response?.data['error'] ?? 'SOMETHING WENT WRONG');
|
||||||
|
}
|
||||||
|
|
||||||
|
throw NetworkException('Network error during verification');
|
||||||
|
} catch (e) {
|
||||||
|
throw UnexpectedException(
|
||||||
|
'Unexpected error during verification: ${e.toString()}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<AuthToken> login(AuthCredentials credentials) async {
|
Future<AuthToken> login(AuthCredentials credentials) async {
|
||||||
try {
|
try {
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class BranchService {
|
|||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return Branch.listFromJson(response.data);
|
return Branch.listFromJson(response.data);
|
||||||
} else {
|
} else {
|
||||||
throw Exception("Failed to fetch");
|
throw Exception("Failed to fetch beneficiaries");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -130,36 +130,4 @@ class ChequeService {
|
|||||||
);
|
);
|
||||||
return response.toString();
|
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
102
lib/api/services/send_sms_service.dart
Normal file
102
lib/api/services/send_sms_service.dart
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
import 'package:send_message/send_message.dart' show sendSMS;
|
||||||
|
import 'package:simcards/sim_card.dart';
|
||||||
|
import 'package:simcards/simcards.dart';
|
||||||
|
|
||||||
|
// This enum provides detailed status back to the UI layer.
|
||||||
|
enum PermissionStatusResult { granted, denied, permanentlyDenied, restricted }
|
||||||
|
|
||||||
|
class SmsService {
|
||||||
|
final Simcards _simcards = Simcards();
|
||||||
|
|
||||||
|
/// Handles the requesting of SMS and Phone permissions.
|
||||||
|
/// Returns a detailed status: granted, denied, or permanentlyDenied.
|
||||||
|
Future<PermissionStatusResult> handleSmsPermission() async {
|
||||||
|
var smsStatus = await Permission.sms.status;
|
||||||
|
var phoneStatus = await Permission.phone.status;
|
||||||
|
|
||||||
|
// Check initial status
|
||||||
|
if (smsStatus.isGranted && phoneStatus.isGranted) {
|
||||||
|
return PermissionStatusResult.granted;
|
||||||
|
}
|
||||||
|
if (smsStatus.isPermanentlyDenied || phoneStatus.isPermanentlyDenied) {
|
||||||
|
return PermissionStatusResult.permanentlyDenied;
|
||||||
|
}
|
||||||
|
if (smsStatus.isRestricted || phoneStatus.isRestricted) {
|
||||||
|
return PermissionStatusResult.restricted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request permissions if not granted
|
||||||
|
print("Requesting SMS and Phone permissions...");
|
||||||
|
await [Permission.phone, Permission.sms].request();
|
||||||
|
|
||||||
|
// Re-check status after request
|
||||||
|
smsStatus = await Permission.sms.status;
|
||||||
|
phoneStatus = await Permission.phone.status;
|
||||||
|
|
||||||
|
if (smsStatus.isGranted && phoneStatus.isGranted) {
|
||||||
|
return PermissionStatusResult.granted;
|
||||||
|
}
|
||||||
|
if (smsStatus.isPermanentlyDenied || phoneStatus.isPermanentlyDenied) {
|
||||||
|
return PermissionStatusResult.permanentlyDenied;
|
||||||
|
}
|
||||||
|
if (smsStatus.isRestricted || phoneStatus.isRestricted) {
|
||||||
|
return PermissionStatusResult.restricted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If none of the above, it's denied
|
||||||
|
return PermissionStatusResult.denied;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to send a single verification SMS.
|
||||||
|
/// This should only be called AFTER permissions have been granted.
|
||||||
|
Future<bool> sendVerificationSms({
|
||||||
|
required BuildContext context,
|
||||||
|
required String destinationNumber,
|
||||||
|
required String message,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
List<SimCard> simCardList = await _simcards.getSimCards();
|
||||||
|
if (simCardList.isEmpty) {
|
||||||
|
print("No SIM card detected.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return await _sendSms(destinationNumber, message, simCardList.first);
|
||||||
|
} catch (e) {
|
||||||
|
print("An error occurred in the SMS process: $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Private function to perform the SMS sending action.
|
||||||
|
Future<bool> _sendSms(
|
||||||
|
String destinationNumber, String message, SimCard selectedSim) async {
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
try {
|
||||||
|
String smsMessage = message;
|
||||||
|
String result = await sendSMS(
|
||||||
|
message: smsMessage,
|
||||||
|
recipients: [destinationNumber],
|
||||||
|
sendDirect: true,
|
||||||
|
);
|
||||||
|
print("Background SMS send attempt result: $result");
|
||||||
|
|
||||||
|
if (result.toLowerCase().contains('sent')) {
|
||||||
|
print("Success: SMS appears to have been sent.");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
print("Failure: SMS was not sent. Result: $result");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Error attempting to send SMS directly: $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("SMS sending is only supported on Android.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,244 +0,0 @@
|
|||||||
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),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
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.'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
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"),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
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"),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class TermDepositScreen extends StatelessWidget {
|
|
||||||
const TermDepositScreen({
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text("Term Deposit (TD)"),
|
|
||||||
),
|
|
||||||
body: const Center(
|
|
||||||
child: Text("Term Deposit (TD)"),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@ import 'package:kmobile/app.dart';
|
|||||||
import 'package:kmobile/features/auth/screens/mpin_screen.dart';
|
import 'package:kmobile/features/auth/screens/mpin_screen.dart';
|
||||||
import 'package:kmobile/features/auth/screens/set_password_screen.dart';
|
import 'package:kmobile/features/auth/screens/set_password_screen.dart';
|
||||||
import 'package:kmobile/features/auth/screens/tnc_required_screen.dart';
|
import 'package:kmobile/features/auth/screens/tnc_required_screen.dart';
|
||||||
|
import 'package:kmobile/features/auth/screens/verification_screen.dart';
|
||||||
import 'package:kmobile/widgets/tnc_dialog.dart';
|
import 'package:kmobile/widgets/tnc_dialog.dart';
|
||||||
import '../../../l10n/app_localizations.dart';
|
import '../../../l10n/app_localizations.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -30,14 +31,25 @@ class LoginScreenState extends State<LoginScreen>
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _submitForm() {
|
void _submitForm() async {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
|
final bool? verificationSuccess = await Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => VerificationScreen(
|
||||||
|
customerNo: _customerNumberController.text.trim(),
|
||||||
|
password: _passwordController.text,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (verificationSuccess == true && mounted) {
|
||||||
context.read<AuthCubit>().login(
|
context.read<AuthCubit>().login(
|
||||||
_customerNumberController.text.trim(),
|
_customerNumberController.text.trim(),
|
||||||
_passwordController.text,
|
_passwordController.text,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
177
lib/features/auth/screens/sms_verification_helper.dart
Normal file
177
lib/features/auth/screens/sms_verification_helper.dart
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:kmobile/api/services/send_sms_service.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
class SmsVerificationHelper {
|
||||||
|
final SmsService _smsService = SmsService();
|
||||||
|
|
||||||
|
Future<void> _showPermanentlyDeniedDialog(BuildContext context) async {
|
||||||
|
await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text("Permission Required"),
|
||||||
|
content: const Text(
|
||||||
|
"SMS and Phone permissions are required for device verification. Please enable them in your app settings to continue."),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
child: const Text("Cancel"),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: const Text("Open Settings"),
|
||||||
|
onPressed: () {
|
||||||
|
openAppSettings(); // Opens the phone's settings screen for this app
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showRestrictedSmsDialog(BuildContext context) async {
|
||||||
|
await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text("SMS Permission Restricted"),
|
||||||
|
content: const SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"It seems your device is restricting this app from sending SMS messages, which is required for verification. Please follow these steps to enable it:\n"),
|
||||||
|
Text("1. Open your device Settings.",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
Text("2. Go to 'Apps' or 'Apps & notifications'."),
|
||||||
|
Text("3. Find and tap on this app ('KMobile')."),
|
||||||
|
Text("4. Tap on the three dots (⋮) in the top right corner."),
|
||||||
|
Text(
|
||||||
|
"5. Select 'Allow restricted settings' and confirm. This is crucial to allow SMS permission."),
|
||||||
|
Text("6. Now you have two options to allow SMS permission:"),
|
||||||
|
Text(
|
||||||
|
" a. Tap on 'Permissions', then find 'SMS' is set to 'Allow'."),
|
||||||
|
Text(
|
||||||
|
" b. Alternatively, you can return to the KMobile app, and the SMS permission pop-up should appear again, allowing you to grant it directly."),
|
||||||
|
Text(
|
||||||
|
"\nSome devices have an additional setting for 'Premium SMS'. If the above doesn't work, look for a 'Premium SMS access' setting (you can search for it in your Settings app) and set it to 'Always Allow' for this app.\n"),
|
||||||
|
Text(
|
||||||
|
"After you've enabled the permission, please come back to the app."),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
child: const Text("I've Enabled It"),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> initiateSmsSequence({
|
||||||
|
required BuildContext context,
|
||||||
|
}) async {
|
||||||
|
bool hasPermission = false;
|
||||||
|
|
||||||
|
// --- PERMISSION LOOP ---
|
||||||
|
while (!hasPermission) {
|
||||||
|
// handleSmsPermission will check the status and request if not granted.
|
||||||
|
final status = await _smsService.handleSmsPermission();
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case PermissionStatusResult.granted:
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text("Permissions Granted! Proceeding..."),
|
||||||
|
duration: Duration(seconds: 2)),
|
||||||
|
);
|
||||||
|
hasPermission = true; // This will break the loop
|
||||||
|
break;
|
||||||
|
case PermissionStatusResult.denied:
|
||||||
|
// The user denied the permission. We show a dialog to explain why we need it
|
||||||
|
// and give them a chance to cancel or let the loop try again.
|
||||||
|
final tryAgain = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text("Permission Required"),
|
||||||
|
content: const Text(
|
||||||
|
"This app requires SMS and Phone permissions to verify your device. Please grant the permissions to continue."),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
child: const Text("Cancel"),
|
||||||
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: const Text("Try Again"),
|
||||||
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (tryAgain != true) {
|
||||||
|
return null; // User chose to cancel.
|
||||||
|
}
|
||||||
|
// If they chose "Try Again", the loop will repeat.
|
||||||
|
break;
|
||||||
|
case PermissionStatusResult.permanentlyDenied:
|
||||||
|
await _showPermanentlyDeniedDialog(context);
|
||||||
|
// Give user time to come back from settings
|
||||||
|
await Future.delayed(const Duration(seconds: 5));
|
||||||
|
// The loop will repeat and re-check the status.
|
||||||
|
break;
|
||||||
|
case PermissionStatusResult.restricted:
|
||||||
|
await _showRestrictedSmsDialog(context);
|
||||||
|
// Give user time to come back from settings
|
||||||
|
await Future.delayed(const Duration(seconds: 5));
|
||||||
|
// The loop will repeat and re-check the status.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- SMS SENDING LOOP ---
|
||||||
|
// This part will only be reached if hasPermission is true.
|
||||||
|
int retries = 3;
|
||||||
|
while (retries > 0) {
|
||||||
|
var uuid = const Uuid();
|
||||||
|
String uniqueId = uuid.v4();
|
||||||
|
String smsMessage = uniqueId;
|
||||||
|
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content:
|
||||||
|
Text("Attempting to send verification SMS... (${4 - retries})"),
|
||||||
|
duration: const Duration(seconds: 2)),
|
||||||
|
);
|
||||||
|
|
||||||
|
bool isSmsSent = await _smsService.sendVerificationSms(
|
||||||
|
context: context,
|
||||||
|
destinationNumber: '9580079717', // Replace with your number
|
||||||
|
message: smsMessage,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isSmsSent) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text("SMS sent successfully!"),
|
||||||
|
duration: Duration(seconds: 2)),
|
||||||
|
);
|
||||||
|
return uniqueId;
|
||||||
|
} else {
|
||||||
|
retries--;
|
||||||
|
if (retries > 0) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text("SMS failed to send. Retrying in 5 seconds..."),
|
||||||
|
duration: Duration(seconds: 4)),
|
||||||
|
);
|
||||||
|
await Future.delayed(const Duration(seconds: 5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all retries fail
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
170
lib/features/auth/screens/verification_screen.dart
Normal file
170
lib/features/auth/screens/verification_screen.dart
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:kmobile/api/services/auth_service.dart';
|
||||||
|
import 'package:kmobile/di/injection.dart';
|
||||||
|
import 'package:kmobile/features/auth/screens/sms_verification_helper.dart';
|
||||||
|
|
||||||
|
class VerificationScreen extends StatefulWidget {
|
||||||
|
final String customerNo;
|
||||||
|
final String password;
|
||||||
|
|
||||||
|
const VerificationScreen({
|
||||||
|
super.key,
|
||||||
|
required this.customerNo,
|
||||||
|
required this.password,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<VerificationScreen> createState() => _VerificationScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VerificationScreenState extends State<VerificationScreen> {
|
||||||
|
String _statusMessage = "Starting verification...";
|
||||||
|
Timer? _timer;
|
||||||
|
int _countdown = 120;
|
||||||
|
bool _isVerifying = false;
|
||||||
|
bool _verificationFailed = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_startVerificationProcess();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_timer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startTimer() {
|
||||||
|
_timer?.cancel(); // Cancel any existing timer
|
||||||
|
_countdown = 120;
|
||||||
|
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||||
|
if (_countdown > 0) {
|
||||||
|
setState(() {
|
||||||
|
_countdown--;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_timer?.cancel();
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_statusMessage = "Verification timed out. Please try again.";
|
||||||
|
_isVerifying = false;
|
||||||
|
_verificationFailed = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _startVerificationProcess() async {
|
||||||
|
setState(() {
|
||||||
|
_isVerifying = true;
|
||||||
|
_verificationFailed = false;
|
||||||
|
_statusMessage = "Starting verification...";
|
||||||
|
});
|
||||||
|
_startTimer();
|
||||||
|
|
||||||
|
// 1. Send SMS
|
||||||
|
setState(() {
|
||||||
|
_statusMessage = "SMS sending...";
|
||||||
|
});
|
||||||
|
final smsHelper = SmsVerificationHelper();
|
||||||
|
final uuid = await smsHelper.initiateSmsSequence(context: context);
|
||||||
|
|
||||||
|
if (uuid != null && mounted) {
|
||||||
|
// SMS sending was successful, now wait before verifying.
|
||||||
|
setState(() {
|
||||||
|
_statusMessage = "SMS sent. Waiting for network delivery...";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Adding a 10-second delay to account for SMS network latency.
|
||||||
|
await Future.delayed(const Duration(seconds: 10));
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
// 2. Verify SIM
|
||||||
|
setState(() {
|
||||||
|
_statusMessage = "Verifying with server...";
|
||||||
|
});
|
||||||
|
|
||||||
|
final authService = getIt<AuthService>();
|
||||||
|
try {
|
||||||
|
await authService.simVerify(uuid, widget.customerNo);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_statusMessage = "Verification successful!";
|
||||||
|
_isVerifying = false;
|
||||||
|
});
|
||||||
|
_timer?.cancel();
|
||||||
|
|
||||||
|
// Pop with success result
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
} catch (e) {
|
||||||
|
setState(() {
|
||||||
|
if(e.toString().contains("NOT_VERIFIED")){
|
||||||
|
_statusMessage = "SIM details not found. Please ensure your mobile number is registered with the bank.";
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
_statusMessage = e.toString();
|
||||||
|
}
|
||||||
|
_isVerifying = false;
|
||||||
|
_verificationFailed = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_statusMessage =
|
||||||
|
"SMS sending failed. Please check permissions and try again.";
|
||||||
|
_isVerifying = false;
|
||||||
|
_verificationFailed = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text("Device Verification"),
|
||||||
|
automaticallyImplyLeading: !_isVerifying,
|
||||||
|
),
|
||||||
|
body: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
if (_isVerifying) const CircularProgressIndicator(),
|
||||||
|
if (!_isVerifying && _verificationFailed)
|
||||||
|
const Icon(Icons.error_outline, color: Colors.red, size: 50),
|
||||||
|
if (!_isVerifying && !_verificationFailed)
|
||||||
|
const Icon(Icons.check_circle_outline,
|
||||||
|
color: Colors.green, size: 50),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
Text(
|
||||||
|
_statusMessage,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(fontSize: 18),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
if (_isVerifying)
|
||||||
|
Text(
|
||||||
|
"Time remaining: $_countdown seconds",
|
||||||
|
style: const TextStyle(fontSize: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
if (_verificationFailed && !_isVerifying) ...[
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _startVerificationProcess,
|
||||||
|
child: const Text('Retry'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -90,12 +90,12 @@ class CardTile extends StatelessWidget {
|
|||||||
const Text(
|
const Text(
|
||||||
"Kangra Central Co-operative Bank",
|
"Kangra Central Co-operative Bank",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Color.fromARGB(255, 238, 237, 237),
|
color: Colors.white,
|
||||||
fontSize: 15,
|
fontSize: 15.5,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
overflow: TextOverflow.visible,
|
overflow: TextOverflow.ellipsis,
|
||||||
maxLines: 2,
|
maxLines: 1,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class _CardManagementScreen extends State<CardManagementScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
title: Text(
|
title: Text(
|
||||||
AppLocalizations.of(context).cardManagement,
|
AppLocalizations.of(context).cardManagement,
|
||||||
),
|
),
|
||||||
@@ -60,7 +61,7 @@ class _CardManagementScreen extends State<CardManagementScreen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
disabled: false,
|
disabled: true,
|
||||||
),
|
),
|
||||||
Divider(height: 1, color: Theme.of(context).dividerColor),
|
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||||
CardManagementTile(
|
CardManagementTile(
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class _CardPinChangeDetailsScreen extends State<CardPinChangeDetailsScreen> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(
|
title: Text(
|
||||||
AppLocalizations.of(context).changeCardPin,
|
AppLocalizations.of(context).cardDetails,
|
||||||
),
|
),
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
),
|
),
|
||||||
@@ -66,8 +66,12 @@ class _CardPinChangeDetailsScreen extends State<CardPinChangeDetailsScreen> {
|
|||||||
isDense: true,
|
isDense: true,
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: Theme.of(context).scaffoldBackgroundColor,
|
fillColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
enabledBorder: const OutlineInputBorder(),
|
enabledBorder: const OutlineInputBorder(
|
||||||
focusedBorder: const OutlineInputBorder(),
|
borderSide: BorderSide(color: Colors.black),
|
||||||
|
),
|
||||||
|
focusedBorder: const OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: Colors.black, width: 2),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
@@ -88,8 +92,13 @@ class _CardPinChangeDetailsScreen extends State<CardPinChangeDetailsScreen> {
|
|||||||
filled: true,
|
filled: true,
|
||||||
fillColor:
|
fillColor:
|
||||||
Theme.of(context).scaffoldBackgroundColor,
|
Theme.of(context).scaffoldBackgroundColor,
|
||||||
enabledBorder: const OutlineInputBorder(),
|
enabledBorder: const OutlineInputBorder(
|
||||||
focusedBorder: const OutlineInputBorder(),
|
borderSide: BorderSide(color: Colors.black),
|
||||||
|
),
|
||||||
|
focusedBorder: const OutlineInputBorder(
|
||||||
|
borderSide:
|
||||||
|
BorderSide(color: Colors.black, width: 2),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
@@ -114,8 +123,13 @@ class _CardPinChangeDetailsScreen extends State<CardPinChangeDetailsScreen> {
|
|||||||
filled: true,
|
filled: true,
|
||||||
fillColor:
|
fillColor:
|
||||||
Theme.of(context).scaffoldBackgroundColor,
|
Theme.of(context).scaffoldBackgroundColor,
|
||||||
enabledBorder: const OutlineInputBorder(),
|
enabledBorder: const OutlineInputBorder(
|
||||||
focusedBorder: const OutlineInputBorder(),
|
borderSide: BorderSide(color: Colors.black),
|
||||||
|
),
|
||||||
|
focusedBorder: const OutlineInputBorder(
|
||||||
|
borderSide:
|
||||||
|
BorderSide(color: Colors.black, width: 2),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
validator: (value) => value != null &&
|
validator: (value) => value != null &&
|
||||||
value.isNotEmpty
|
value.isNotEmpty
|
||||||
@@ -135,8 +149,12 @@ class _CardPinChangeDetailsScreen extends State<CardPinChangeDetailsScreen> {
|
|||||||
isDense: true,
|
isDense: true,
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: Theme.of(context).scaffoldBackgroundColor,
|
fillColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
enabledBorder: const OutlineInputBorder(),
|
enabledBorder: const OutlineInputBorder(
|
||||||
focusedBorder: const OutlineInputBorder(),
|
borderSide: BorderSide(color: Colors.black),
|
||||||
|
),
|
||||||
|
focusedBorder: const OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: Colors.black, width: 2),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
textInputAction: TextInputAction.done,
|
textInputAction: TextInputAction.done,
|
||||||
keyboardType: TextInputType.phone,
|
keyboardType: TextInputType.phone,
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:kmobile/data/models/user.dart';
|
import 'package:kmobile/data/models/user.dart';
|
||||||
import 'package:kmobile/features/cheque/screens/cheque_enquiry_screen.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:kmobile/features/cheque/screens/stop_cheque_screen.dart';
|
||||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||||
import '../../../l10n/app_localizations.dart';
|
import '../../../l10n/app_localizations.dart';
|
||||||
@@ -44,6 +42,8 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
|
|||||||
child: ChequeManagementCardTile(
|
child: ChequeManagementCardTile(
|
||||||
icon: Symbols.payments,
|
icon: Symbols.payments,
|
||||||
label: AppLocalizations.of(context).chequeEnquiryTitle,
|
label: AppLocalizations.of(context).chequeEnquiryTitle,
|
||||||
|
subtitle:
|
||||||
|
AppLocalizations.of(context).chequeEnquirySubtitle,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
@@ -61,6 +61,7 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
|
|||||||
child: ChequeManagementCardTile(
|
child: ChequeManagementCardTile(
|
||||||
icon: Symbols.block_sharp,
|
icon: Symbols.block_sharp,
|
||||||
label: AppLocalizations.of(context).stopCheque,
|
label: AppLocalizations.of(context).stopCheque,
|
||||||
|
subtitle: AppLocalizations.of(context).stopChequeSubtitle,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
@@ -74,40 +75,6 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
|
||||||
child: ChequeManagementCardTile(
|
|
||||||
icon: Symbols.stop_circle,
|
|
||||||
label: AppLocalizations.of(context).revokeStop,
|
|
||||||
onTap: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => RevokeStopChequeScreen(
|
|
||||||
users: users,
|
|
||||||
selectedIndex: selectedAccountIndex,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: ChequeManagementCardTile(
|
|
||||||
icon: Symbols.check_circle, // Using check_circle for Positive Pay
|
|
||||||
label: AppLocalizations.of(context).positivePayTitle,
|
|
||||||
onTap: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => PositivePayScreen(
|
|
||||||
users: users,
|
|
||||||
selectedIndex: selectedAccountIndex,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -134,6 +101,7 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
|
|||||||
class ChequeManagementCardTile extends StatelessWidget {
|
class ChequeManagementCardTile extends StatelessWidget {
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final String label;
|
final String label;
|
||||||
|
final String? subtitle;
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
final bool disable;
|
final bool disable;
|
||||||
|
|
||||||
@@ -141,6 +109,7 @@ class ChequeManagementCardTile extends StatelessWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.label,
|
required this.label,
|
||||||
|
this.subtitle,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
this.disable = false,
|
this.disable = false,
|
||||||
});
|
});
|
||||||
@@ -183,6 +152,19 @@ class ChequeManagementCardTile extends StatelessWidget {
|
|||||||
: theme.colorScheme.onSurface,
|
: 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,162 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:kmobile/data/models/user.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> {
|
|
||||||
final _formKey = GlobalKey<FormState>();
|
|
||||||
final _accountNumberController = TextEditingController();
|
|
||||||
final _chequeNumberController = TextEditingController();
|
|
||||||
final _chequeDateController = TextEditingController();
|
|
||||||
final _amountController = TextEditingController();
|
|
||||||
final _payeeController = TextEditingController();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
// Pre-fill the account number if possible
|
|
||||||
if (widget.users.isNotEmpty) {
|
|
||||||
_accountNumberController.text = widget.users[widget.selectedIndex].accountNo!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_accountNumberController.dispose();
|
|
||||||
_chequeNumberController.dispose();
|
|
||||||
_chequeDateController.dispose();
|
|
||||||
_amountController.dispose();
|
|
||||||
_payeeController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@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),
|
|
||||||
TextFormField(
|
|
||||||
controller: _accountNumberController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: AppLocalizations.of(context).accountNumber,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return 'Please enter account number';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16.0),
|
|
||||||
TextFormField(
|
|
||||||
controller: _chequeNumberController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: AppLocalizations.of(context).chequeNumberLabel,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return AppLocalizations.of(context).pleaseEnterChequeNumber;
|
|
||||||
}
|
|
||||||
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.day}-${pickedDate.month}-${pickedDate.year}";
|
|
||||||
_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
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text(AppLocalizations.of(context).processingData)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Text(AppLocalizations.of(context).proceedButton),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,338 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:kmobile/api/services/cheque_service.dart';
|
|
||||||
import 'package:kmobile/data/models/user.dart';
|
|
||||||
import 'package:kmobile/di/injection.dart';
|
|
||||||
import 'package:kmobile/features/fund_transfer/screens/transaction_pin_screen.dart';
|
|
||||||
import 'package:kmobile/l10n/app_localizations.dart';
|
|
||||||
|
|
||||||
class RevokeStopMultipleChequesScreen extends StatefulWidget {
|
|
||||||
final User selectedAccount;
|
|
||||||
final String date;
|
|
||||||
final String instrType;
|
|
||||||
final String fromCheque;
|
|
||||||
final String toCheque;
|
|
||||||
|
|
||||||
const RevokeStopMultipleChequesScreen(
|
|
||||||
{super.key,
|
|
||||||
required this.selectedAccount,
|
|
||||||
required this.date,
|
|
||||||
required this.instrType,
|
|
||||||
required this.fromCheque,
|
|
||||||
required this.toCheque});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<RevokeStopMultipleChequesScreen> createState() =>
|
|
||||||
_RevokeStopMultipleChequesScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RevokeStopMultipleChequesScreenState extends State<RevokeStopMultipleChequesScreen> {
|
|
||||||
final _formKey = GlobalKey<FormState>();
|
|
||||||
final _stopFromChequeNoController = TextEditingController();
|
|
||||||
final _stopToChequeNoController = TextEditingController();
|
|
||||||
final _stopIssueDateController = TextEditingController();
|
|
||||||
final _stopExpiryDateController = TextEditingController();
|
|
||||||
final _stopAmountController = TextEditingController();
|
|
||||||
final _chequeService = getIt<ChequeService>();
|
|
||||||
|
|
||||||
String? _selectedComment;
|
|
||||||
final _otherCommentController = TextEditingController();
|
|
||||||
bool _showOtherCommentField = false;
|
|
||||||
final List<String> _commentOptions = [
|
|
||||||
'Cheque Found',
|
|
||||||
'Cheque Fixed',
|
|
||||||
'Other'
|
|
||||||
];
|
|
||||||
|
|
||||||
Future<void> _selectDate(TextEditingController controller) async {
|
|
||||||
final DateTime? picked = await showDatePicker(
|
|
||||||
context: context,
|
|
||||||
initialDate: DateTime.now(),
|
|
||||||
firstDate: DateTime.now(),
|
|
||||||
lastDate: DateTime(2101),
|
|
||||||
);
|
|
||||||
if (picked != null) {
|
|
||||||
setState(() {
|
|
||||||
controller.text =
|
|
||||||
'${picked.day.toString().padLeft(2, '0')}/${picked.month.toString().padLeft(2, '0')}/${picked.year}';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _showResponseDialog(String title, String message) async {
|
|
||||||
return showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
barrierDismissible: false, // user must tap button!
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: Text(title),
|
|
||||||
content: SingleChildScrollView(
|
|
||||||
child: ListBody(
|
|
||||||
children: <Widget>[
|
|
||||||
Text(message),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: <Widget>[
|
|
||||||
TextButton(
|
|
||||||
child: const Text('Close'),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text(AppLocalizations.of(context).revokeMultipleStops),
|
|
||||||
),
|
|
||||||
body: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Form(
|
|
||||||
key: _formKey,
|
|
||||||
child: ListView(
|
|
||||||
children: [
|
|
||||||
Card(
|
|
||||||
elevation: 0,
|
|
||||||
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
|
||||||
child: ListTile(
|
|
||||||
leading: Image.asset(
|
|
||||||
'assets/images/logo.png',
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
),
|
|
||||||
title: Text(widget.selectedAccount.accountNo!),
|
|
||||||
subtitle:
|
|
||||||
Text(AppLocalizations.of(context).accountNumberTitle),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
TextFormField(
|
|
||||||
controller: _stopFromChequeNoController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: AppLocalizations.of(context).fromChequeNumberHint,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
errorMaxLines: 2,
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return AppLocalizations.of(context)
|
|
||||||
.pleaseEnterChequeNumberError;
|
|
||||||
}
|
|
||||||
final chequeNumber = int.tryParse(value);
|
|
||||||
final fromCheque = int.tryParse(widget.fromCheque);
|
|
||||||
final toCheque = int.tryParse(widget.toCheque);
|
|
||||||
if (chequeNumber == null ||
|
|
||||||
fromCheque == null ||
|
|
||||||
toCheque == null) {
|
|
||||||
return AppLocalizations.of(context)
|
|
||||||
.invalidChequeNumberFormatError;
|
|
||||||
}
|
|
||||||
// if (chequeNumber < fromCheque || chequeNumber > toCheque) {
|
|
||||||
// return AppLocalizations.of(context).chequeNumberRangeError(
|
|
||||||
// widget.fromCheque, widget.toCheque);
|
|
||||||
// }
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: _stopToChequeNoController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: AppLocalizations.of(context).toChequeNumberHint,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
errorMaxLines: 2,
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return AppLocalizations.of(context)
|
|
||||||
.pleaseEnterChequeNumberError;
|
|
||||||
}
|
|
||||||
final chequeNumber = int.tryParse(value);
|
|
||||||
final fromCheque = int.tryParse(widget.fromCheque);
|
|
||||||
final toCheque = int.tryParse(widget.toCheque);
|
|
||||||
if (chequeNumber == null ||
|
|
||||||
fromCheque == null ||
|
|
||||||
toCheque == null) {
|
|
||||||
return AppLocalizations.of(context)
|
|
||||||
.invalidChequeNumberFormatError;
|
|
||||||
}
|
|
||||||
// if (chequeNumber < fromCheque || chequeNumber > toCheque) {
|
|
||||||
// return AppLocalizations.of(context).chequeNumberRangeError(
|
|
||||||
// widget.fromCheque, widget.toCheque);
|
|
||||||
// }
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
initialValue: widget.instrType,
|
|
||||||
readOnly: true,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: AppLocalizations.of(context).instrumentTypeLabel,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: _stopIssueDateController,
|
|
||||||
readOnly: true,
|
|
||||||
onTap: () => _selectDate(_stopIssueDateController),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: AppLocalizations.of(context).revokeIssueDate,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: const Icon(Icons.calendar_today),
|
|
||||||
onPressed: () => _selectDate(_stopIssueDateController),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.datetime,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: _stopExpiryDateController,
|
|
||||||
readOnly: true,
|
|
||||||
onTap: () => _selectDate(_stopExpiryDateController),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: AppLocalizations.of(context).revokeExpiryDate,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: const Icon(Icons.calendar_today),
|
|
||||||
onPressed: () => _selectDate(_stopExpiryDateController),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.datetime,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: _stopAmountController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: AppLocalizations.of(context).revokeAmount,
|
|
||||||
prefixText: '₹ ',
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
DropdownButtonFormField<String>(
|
|
||||||
value: _selectedComment,
|
|
||||||
items: _commentOptions.map((String value) {
|
|
||||||
return DropdownMenuItem<String>(
|
|
||||||
value: value,
|
|
||||||
child: Text(value),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
onChanged: (newValue) {
|
|
||||||
setState(() {
|
|
||||||
_selectedComment = newValue;
|
|
||||||
_showOtherCommentField = newValue == 'Other';
|
|
||||||
});
|
|
||||||
},
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: AppLocalizations.of(context).revokeComment,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (_showOtherCommentField)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 16.0),
|
|
||||||
child: TextFormField(
|
|
||||||
controller: _otherCommentController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: "Other Reasons :",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
validator: (value) {
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 32),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
if (_formKey.currentState!.validate()) {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => TransactionPinScreen(
|
|
||||||
onPinCompleted: (ctx, pin) async {
|
|
||||||
Navigator.pop(context);
|
|
||||||
try {
|
|
||||||
final response = await _chequeService.revokeStop(
|
|
||||||
accountno: widget.selectedAccount.accountNo!,
|
|
||||||
removeFromChequeNo:
|
|
||||||
_stopFromChequeNoController.text,
|
|
||||||
instrType: widget.instrType,
|
|
||||||
removeToChequeNo:
|
|
||||||
_stopToChequeNoController.text,
|
|
||||||
removeIssueDate: _stopIssueDateController.text,
|
|
||||||
removeExpiryDate: _stopExpiryDateController.text,
|
|
||||||
removeAmount: _stopAmountController.text,
|
|
||||||
removeComment: _selectedComment == 'Other'
|
|
||||||
? _otherCommentController.text
|
|
||||||
: _selectedComment ?? '',
|
|
||||||
tpin: pin,
|
|
||||||
);
|
|
||||||
if (!mounted) return;
|
|
||||||
final decodedResponse = jsonDecode(response);
|
|
||||||
String responseString = response.toString(); // used as the case only for incorrect TPIN
|
|
||||||
final status = decodedResponse['status'];
|
|
||||||
final message = decodedResponse['message'];
|
|
||||||
final code = decodedResponse['code'];
|
|
||||||
if (status == 'SUCCESS') {
|
|
||||||
_showResponseDialog('Success', message);
|
|
||||||
} if (status == 'ERROR') {
|
|
||||||
String errMessage = "error";
|
|
||||||
if(code == '0172') {
|
|
||||||
errMessage = 'The selected Cheque is not stopped';
|
|
||||||
} else if(code == '0748') {
|
|
||||||
errMessage = 'The selected Cheque is already presented';
|
|
||||||
}
|
|
||||||
_showResponseDialog('Error', errMessage);
|
|
||||||
}
|
|
||||||
if(responseString.contains('INCORRECT_TPIN')){
|
|
||||||
_showResponseDialog('Invalid TPIN',
|
|
||||||
'The TPIN you entered is incorrect. Please try again.');
|
|
||||||
}
|
|
||||||
} on Exception catch (e) {
|
|
||||||
try {
|
|
||||||
final errorBodyString =
|
|
||||||
e.toString().split('Exception: ')[1];
|
|
||||||
final errorBody = jsonDecode(errorBodyString);
|
|
||||||
if (errorBody.containsKey('error') &&
|
|
||||||
errorBody['error'] == 'INCORRECT_TPIN') {
|
|
||||||
_showResponseDialog('Invalid TPIN',
|
|
||||||
'The TPIN you entered is incorrect. Please try again.');
|
|
||||||
} else {
|
|
||||||
_showResponseDialog(
|
|
||||||
'Error', 'Internal Server Error');
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
_showResponseDialog(
|
|
||||||
'Error', 'Internal Server Error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Text(AppLocalizations.of(context).revokeStopButton),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,351 +0,0 @@
|
|||||||
import 'package:kmobile/data/models/user.dart';
|
|
||||||
import 'package:kmobile/di/injection.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:kmobile/api/services/cheque_service.dart';
|
|
||||||
import 'package:kmobile/features/cheque/screens/revoke%20_stop_multiple_screen.dart';
|
|
||||||
import 'package:kmobile/features/cheque/screens/revoke_stop_single_screen.dart';
|
|
||||||
import 'package:kmobile/l10n/app_localizations.dart';
|
|
||||||
|
|
||||||
class RevokeStopChequeScreen extends StatefulWidget {
|
|
||||||
final List<User> users;
|
|
||||||
final int selectedIndex;
|
|
||||||
|
|
||||||
const RevokeStopChequeScreen(
|
|
||||||
{
|
|
||||||
super.key,
|
|
||||||
required this.users,
|
|
||||||
required this.selectedIndex,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<RevokeStopChequeScreen> createState() => _RevokeStopChequeScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RevokeStopChequeScreenState extends State<RevokeStopChequeScreen> {
|
|
||||||
User? _selectedAccount;
|
|
||||||
var service = getIt<ChequeService>();
|
|
||||||
bool _isLoading = true;
|
|
||||||
List<Cheque> _stCheques = [];
|
|
||||||
List<User> _filteredUsers = [];
|
|
||||||
|
|
||||||
@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();
|
|
||||||
setState(() {
|
|
||||||
_stCheques = stCheques;
|
|
||||||
_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: _stCheques.first.fromCheque!,
|
|
||||||
toCheque: _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: _stCheques.first.fromCheque!,
|
|
||||||
toCheque: _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 ?? ''),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,304 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:kmobile/data/models/user.dart';
|
|
||||||
import 'package:kmobile/di/injection.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:kmobile/api/services/cheque_service.dart';
|
|
||||||
import 'package:kmobile/features/fund_transfer/screens/transaction_pin_screen.dart';
|
|
||||||
import 'package:kmobile/l10n/app_localizations.dart';
|
|
||||||
|
|
||||||
class RevokeStopSingleChequeScreen extends StatefulWidget {
|
|
||||||
final User selectedAccount;
|
|
||||||
final String date;
|
|
||||||
final String instrType;
|
|
||||||
final String fromCheque;
|
|
||||||
final String toCheque;
|
|
||||||
|
|
||||||
const RevokeStopSingleChequeScreen(
|
|
||||||
{super.key,
|
|
||||||
required this.selectedAccount,
|
|
||||||
required this.date,
|
|
||||||
required this.instrType,
|
|
||||||
required this.fromCheque,
|
|
||||||
required this.toCheque});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<RevokeStopSingleChequeScreen> createState() => _RevokeStopSingleChequeScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RevokeStopSingleChequeScreenState extends State<RevokeStopSingleChequeScreen> {
|
|
||||||
final _formKey = GlobalKey<FormState>();
|
|
||||||
final _stopFromChequeNoController = TextEditingController();
|
|
||||||
final _stopIssueDateController = TextEditingController();
|
|
||||||
final _stopExpiryDateController = TextEditingController();
|
|
||||||
final _stopAmountController = TextEditingController();
|
|
||||||
final _chequeService = getIt<ChequeService>();
|
|
||||||
|
|
||||||
String? _selectedComment;
|
|
||||||
final _otherCommentController = TextEditingController();
|
|
||||||
bool _showOtherCommentField = false;
|
|
||||||
final List<String> _commentOptions = [
|
|
||||||
'Cheque Found',
|
|
||||||
'Cheque Fixed',
|
|
||||||
'Other'
|
|
||||||
];
|
|
||||||
|
|
||||||
Future<void> _selectDate(TextEditingController controller) async {
|
|
||||||
final DateTime? picked = await showDatePicker(
|
|
||||||
context: context,
|
|
||||||
initialDate: DateTime.now(),
|
|
||||||
firstDate: DateTime.now(),
|
|
||||||
lastDate: DateTime(2101),
|
|
||||||
);
|
|
||||||
if (picked != null) {
|
|
||||||
setState(() {
|
|
||||||
controller.text =
|
|
||||||
'${picked.day.toString().padLeft(2, '0')}/${picked.month.toString().padLeft(2, '0')}/${picked.year}';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _showResponseDialog(String title, String message) async {
|
|
||||||
return showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
barrierDismissible: false, // user must tap button!
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: Text(title),
|
|
||||||
content: SingleChildScrollView(
|
|
||||||
child: ListBody(
|
|
||||||
children: <Widget>[
|
|
||||||
Text(message),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: <Widget>[
|
|
||||||
TextButton(
|
|
||||||
child: Text(AppLocalizations.of(context).closeButton),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text(AppLocalizations.of(context).revokeSingleStopTitle)),
|
|
||||||
body: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Form(
|
|
||||||
key: _formKey,
|
|
||||||
child: ListView(
|
|
||||||
children: [
|
|
||||||
Card(
|
|
||||||
elevation: 0,
|
|
||||||
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
|
||||||
child: ListTile(
|
|
||||||
leading: Image.asset(
|
|
||||||
'assets/images/logo.png',
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
),
|
|
||||||
title: Text(widget.selectedAccount.accountNo!),
|
|
||||||
subtitle:
|
|
||||||
Text(AppLocalizations.of(context).accountNumberLabel),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
TextFormField(
|
|
||||||
controller: _stopFromChequeNoController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: AppLocalizations.of(context).chequeNumberLabel,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
errorMaxLines: 2,
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return AppLocalizations.of(context)
|
|
||||||
.pleaseEnterChequeNumberError;
|
|
||||||
}
|
|
||||||
final chequeNumber = int.tryParse(value);
|
|
||||||
final fromCheque = int.tryParse(widget.fromCheque);
|
|
||||||
final toCheque = int.tryParse(widget.toCheque);
|
|
||||||
if (chequeNumber == null ||
|
|
||||||
fromCheque == null ||
|
|
||||||
toCheque == null) {
|
|
||||||
return AppLocalizations.of(context)
|
|
||||||
.invalidChequeNumberFormatError;
|
|
||||||
}
|
|
||||||
// if (chequeNumber < fromCheque || chequeNumber > toCheque) {
|
|
||||||
// return AppLocalizations.of(context).chequeNumberRangeError(
|
|
||||||
// widget.fromCheque, widget.toCheque);
|
|
||||||
// }
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
initialValue: widget.instrType,
|
|
||||||
readOnly: true,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: AppLocalizations.of(context).instrumentTypeLabel,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: _stopIssueDateController,
|
|
||||||
readOnly: true,
|
|
||||||
onTap: () => _selectDate(_stopIssueDateController),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: AppLocalizations.of(context).revokeIssueDate,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: const Icon(Icons.calendar_today),
|
|
||||||
onPressed: () => _selectDate(_stopIssueDateController),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.datetime,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: _stopExpiryDateController,
|
|
||||||
readOnly: true,
|
|
||||||
onTap: () => _selectDate(_stopExpiryDateController),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: AppLocalizations.of(context).revokeExpiryDate,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: const Icon(Icons.calendar_today),
|
|
||||||
onPressed: () => _selectDate(_stopExpiryDateController),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.datetime,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: _stopAmountController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: AppLocalizations.of(context).revokeAmount,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
DropdownButtonFormField<String>(
|
|
||||||
value: _selectedComment,
|
|
||||||
items: _commentOptions.map((String value) {
|
|
||||||
return DropdownMenuItem<String>(
|
|
||||||
value: value,
|
|
||||||
child: Text(value),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
onChanged: (newValue) {
|
|
||||||
setState(() {
|
|
||||||
_selectedComment = newValue;
|
|
||||||
_showOtherCommentField = newValue == 'Other';
|
|
||||||
});
|
|
||||||
},
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: AppLocalizations.of(context).revokeComment,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (_showOtherCommentField)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 16.0),
|
|
||||||
child: TextFormField(
|
|
||||||
controller: _otherCommentController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: "Other Reasons :",
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
validator: (value) {
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 32),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
if (_formKey.currentState!.validate()) {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => TransactionPinScreen(
|
|
||||||
onPinCompleted: (ctx, pin) async {
|
|
||||||
Navigator.pop(context);
|
|
||||||
try {
|
|
||||||
final response = await _chequeService.revokeStop(
|
|
||||||
accountno: widget.selectedAccount.accountNo!,
|
|
||||||
removeFromChequeNo:
|
|
||||||
_stopFromChequeNoController.text,
|
|
||||||
instrType: widget.instrType,
|
|
||||||
removeToChequeNo:
|
|
||||||
_stopFromChequeNoController.text,
|
|
||||||
removeIssueDate: _stopIssueDateController.text,
|
|
||||||
removeExpiryDate: _stopExpiryDateController.text,
|
|
||||||
removeAmount: _stopAmountController.text,
|
|
||||||
removeComment: _selectedComment == 'Other'
|
|
||||||
? _otherCommentController.text
|
|
||||||
: _selectedComment ?? '',
|
|
||||||
tpin: pin,
|
|
||||||
);
|
|
||||||
if (!mounted) return;
|
|
||||||
final decodedResponse = jsonDecode(response);
|
|
||||||
String responseString = response.toString(); // used as the case only for incorrect TPIN
|
|
||||||
final status = decodedResponse['status'];
|
|
||||||
final message = decodedResponse['message'];
|
|
||||||
final code = decodedResponse['code'];
|
|
||||||
if (status == 'SUCCESS') {
|
|
||||||
_showResponseDialog('Success', message);
|
|
||||||
} if (status == 'ERROR') {
|
|
||||||
String errMessage = "error";
|
|
||||||
if(code == '0172') {
|
|
||||||
errMessage = 'The selected Cheque is not stopped';
|
|
||||||
} else if(code == '0748') {
|
|
||||||
errMessage = 'The selected Cheque is already presented';
|
|
||||||
}
|
|
||||||
_showResponseDialog('Error', errMessage);
|
|
||||||
}
|
|
||||||
if(responseString.contains('INCORRECT_TPIN')){
|
|
||||||
_showResponseDialog('Invalid TPIN',
|
|
||||||
'The TPIN you entered is incorrect. Please try again.');
|
|
||||||
}
|
|
||||||
} on DioException catch (e) {
|
|
||||||
try {
|
|
||||||
final errorBodyString =
|
|
||||||
e.toString().split('Exception: ')[1];
|
|
||||||
final errorBody = jsonDecode(errorBodyString);
|
|
||||||
if (errorBody.containsKey('error') &&
|
|
||||||
errorBody['error'] == 'INCORRECT_TPIN') {
|
|
||||||
_showResponseDialog('Invalid TPIN',
|
|
||||||
'The TPIN you entered is incorrect. Please try again.');
|
|
||||||
} else {
|
|
||||||
_showResponseDialog(
|
|
||||||
'Error', 'Internal Server Error');
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
_showResponseDialog(
|
|
||||||
'Error', 'Internal Server Error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Text(AppLocalizations.of(context).revokeStopButton),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -309,7 +309,6 @@ class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
|
|||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final decodedResponse = jsonDecode(response);
|
final decodedResponse = jsonDecode(response);
|
||||||
String responseString = response.toString(); // used as the case only for incorrect TPIN
|
|
||||||
final status = decodedResponse['status'];
|
final status = decodedResponse['status'];
|
||||||
final message = decodedResponse['message'];
|
final message = decodedResponse['message'];
|
||||||
final code = decodedResponse['code'];
|
final code = decodedResponse['code'];
|
||||||
@@ -324,10 +323,6 @@ class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
|
|||||||
}
|
}
|
||||||
_showResponseDialog('Error', errMessage);
|
_showResponseDialog('Error', errMessage);
|
||||||
}
|
}
|
||||||
if(responseString.contains('INCORRECT_TPIN')){
|
|
||||||
_showResponseDialog('Invalid TPIN',
|
|
||||||
'The TPIN you entered is incorrect. Please try again.');
|
|
||||||
}
|
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
try {
|
try {
|
||||||
final errorBodyString =
|
final errorBodyString =
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ class _StopSingleChequeScreenState extends State<StopSingleChequeScreen> {
|
|||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final decodedResponse = jsonDecode(response);
|
final decodedResponse = jsonDecode(response);
|
||||||
String responseString = response.toString(); // used as the case only for incorrect TPIN
|
|
||||||
final status = decodedResponse['status'];
|
final status = decodedResponse['status'];
|
||||||
final message = decodedResponse['message'];
|
final message = decodedResponse['message'];
|
||||||
final code = decodedResponse['code'];
|
final code = decodedResponse['code'];
|
||||||
@@ -293,10 +293,6 @@ class _StopSingleChequeScreenState extends State<StopSingleChequeScreen> {
|
|||||||
}
|
}
|
||||||
_showResponseDialog('Error', errMessage);
|
_showResponseDialog('Error', errMessage);
|
||||||
}
|
}
|
||||||
if(responseString.contains('INCORRECT_TPIN')){
|
|
||||||
_showResponseDialog('Invalid TPIN',
|
|
||||||
'The TPIN you entered is incorrect. Please try again.');
|
|
||||||
}
|
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
try {
|
try {
|
||||||
final errorBodyString =
|
final errorBodyString =
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'package:kmobile/features/account_opening/screens/account_opening_screen.dart';
|
|
||||||
import 'package:kmobile/features/card/screens/card_management_screen.dart';
|
|
||||||
import 'package:kmobile/features/service/screens/atm_locator_screen.dart';
|
import 'package:kmobile/features/service/screens/atm_locator_screen.dart';
|
||||||
import '../../../l10n/app_localizations.dart';
|
import '../../../l10n/app_localizations.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -45,6 +43,7 @@ class _ServiceScreen extends State<ServiceScreen> {
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ServiceManagementTile(
|
child: ServiceManagementTile(
|
||||||
icon: Symbols.question_mark,
|
icon: Symbols.question_mark,
|
||||||
@@ -58,6 +57,7 @@ class _ServiceScreen extends State<ServiceScreen> {
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ServiceManagementTile(
|
child: ServiceManagementTile(
|
||||||
icon: Symbols.location_pin,
|
icon: Symbols.location_pin,
|
||||||
@@ -71,32 +71,6 @@ class _ServiceScreen extends State<ServiceScreen> {
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
|
||||||
child: ServiceManagementTile(
|
|
||||||
icon: Symbols.box,
|
|
||||||
label: "Account Opening",
|
|
||||||
onTap: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const AccountOpeningScreen()));
|
|
||||||
},
|
|
||||||
disabled: false,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: ServiceManagementTile(
|
|
||||||
icon: Symbols.credit_card,
|
|
||||||
label: AppLocalizations.of(context).cardManagement,
|
|
||||||
onTap: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const CardManagementScreen()));
|
|
||||||
},
|
|
||||||
disabled: false,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// No Spacer() needed here as Expanded children will fill space
|
// No Spacer() needed here as Expanded children will fill space
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -148,8 +122,8 @@ class ServiceManagementTile extends StatelessWidget {
|
|||||||
onTap:
|
onTap:
|
||||||
disabled ? null : onTap, // Disable InkWell if the tile is disabled
|
disabled ? null : onTap, // Disable InkWell if the tile is disabled
|
||||||
borderRadius: BorderRadius.circular(12.0),
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
child: Center(
|
child: Padding(
|
||||||
child: SingleChildScrollView(
|
padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@@ -174,7 +148,6 @@ class ServiceManagementTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -555,42 +555,5 @@
|
|||||||
"stopChequeButton": "Stop Cheque",
|
"stopChequeButton": "Stop Cheque",
|
||||||
"stopMultipleChequesTitle": "Stop Multiple Cheques",
|
"stopMultipleChequesTitle": "Stop Multiple Cheques",
|
||||||
"fromChequeNumberHint": "From Cheque Number *",
|
"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"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -556,42 +556,5 @@
|
|||||||
"stopChequeButton": "चेक रोकें",
|
"stopChequeButton": "चेक रोकें",
|
||||||
"stopMultipleChequesTitle": "एकाधिक चेक रोकें",
|
"stopMultipleChequesTitle": "एकाधिक चेक रोकें",
|
||||||
"fromChequeNumberHint": "चेक नंबर से *",
|
"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": "बंद करें"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ void main() async {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Check for device compromise
|
// Check for device compromise
|
||||||
// final compromisedMessage = await SecurityService.deviceCompromisedMessage;
|
final compromisedMessage = await SecurityService.deviceCompromisedMessage;
|
||||||
// if (compromisedMessage != null) {
|
if (compromisedMessage != null) {
|
||||||
// runApp(MaterialApp(
|
runApp(MaterialApp(
|
||||||
// home: SecurityErrorScreen(message: compromisedMessage),
|
home: SecurityErrorScreen(message: compromisedMessage),
|
||||||
// ));
|
));
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
await setupDependencies();
|
await setupDependencies();
|
||||||
runApp(const KMobile());
|
runApp(const KMobile());
|
||||||
}
|
}
|
||||||
|
|||||||
18
pubspec.lock
18
pubspec.lock
@@ -773,6 +773,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.0"
|
||||||
|
send_message:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: send_message
|
||||||
|
sha256: "79b5f69fd3ab0b9e6265f8d972800d7989b3082a0523c7f4b8e38bf4e1c71235"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
share_plus:
|
share_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -861,6 +869,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
|
simcards:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: simcards
|
||||||
|
sha256: b621cc265ebbb3e11009ca9be67063efbc011396c4224aff8b08edaba30fa5ae
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.0.1"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -1003,7 +1019,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.4"
|
version: "3.1.4"
|
||||||
uuid:
|
uuid:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: uuid
|
name: uuid
|
||||||
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.6
|
cupertino_icons: ^1.0.6
|
||||||
jailbreak_root_detection: ^1.1.6
|
jailbreak_root_detection: ^1.1.6
|
||||||
equatable: ^2.0.7
|
equatable: ^2.0.7
|
||||||
dio: ^5.8.0+1
|
dio: ^5.9.0
|
||||||
flutter_secure_storage: ^9.2.4
|
flutter_secure_storage: ^9.2.4
|
||||||
bloc: ^9.0.0
|
bloc: ^9.0.0
|
||||||
flutter_bloc: ^9.1.0
|
flutter_bloc: ^9.1.0
|
||||||
@@ -63,6 +63,9 @@ dependencies:
|
|||||||
package_info_plus: ^4.2.0
|
package_info_plus: ^4.2.0
|
||||||
flutter_local_notifications: ^19.5.0
|
flutter_local_notifications: ^19.5.0
|
||||||
open_filex: ^4.7.0
|
open_filex: ^4.7.0
|
||||||
|
simcards: ^0.0.1
|
||||||
|
uuid: ^4.5.1
|
||||||
|
send_message: ^1.0.0
|
||||||
# jailbreak_root_detection: "^1.1.6"
|
# jailbreak_root_detection: "^1.1.6"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user