8 Commits

Author SHA1 Message Date
89569ab1c3 Not verified case added 2026-01-16 15:49:40 +05:30
5c959ba15c VERIFIED message fixed 2026-01-16 15:18:08 +05:30
d89a4f5109 Test APK with Sim Binding and Cheque 2026-01-16 13:53:03 +05:30
1c3a07bd66 Test APK with cheque #2 2025-12-31 13:52:37 +05:30
d44ee5590e Test APK with cheque 2025-12-31 13:45:19 +05:30
715162b713 Test Sim Binding APK 2025-12-11 12:18:25 +05:30
8149ef2a5b Sim Binding Done #1 2025-12-10 11:50:02 +05:30
1a2dea611b SMS integrated with new ui 2025-12-09 18:11:46 +05:30
31 changed files with 618 additions and 1890 deletions

View File

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

View File

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

View File

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

View File

@@ -130,36 +130,4 @@ class ChequeService {
);
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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ import 'package:kmobile/app.dart';
import 'package:kmobile/features/auth/screens/mpin_screen.dart';
import 'package:kmobile/features/auth/screens/set_password_screen.dart';
import 'package:kmobile/features/auth/screens/tnc_required_screen.dart';
import 'package:kmobile/features/auth/screens/verification_screen.dart';
import 'package:kmobile/widgets/tnc_dialog.dart';
import '../../../l10n/app_localizations.dart';
import 'package:flutter/material.dart';
@@ -30,14 +31,25 @@ class LoginScreenState extends State<LoginScreen>
super.dispose();
}
void _submitForm() {
void _submitForm() async {
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(
_customerNumberController.text.trim(),
_passwordController.text,
);
}
}
}
@override
Widget build(BuildContext context) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,6 @@
import 'package:flutter/material.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/features/cheque/screens/cheque_enquiry_screen.dart';
import 'package:kmobile/features/cheque/screens/positive_pay_screen.dart';
import 'package:kmobile/features/cheque/screens/revoke_stop_screen.dart';
import 'package:kmobile/features/cheque/screens/stop_cheque_screen.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import '../../../l10n/app_localizations.dart';
@@ -44,6 +42,8 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
child: ChequeManagementCardTile(
icon: Symbols.payments,
label: AppLocalizations.of(context).chequeEnquiryTitle,
subtitle:
AppLocalizations.of(context).chequeEnquirySubtitle,
onTap: () {
Navigator.push(
context,
@@ -61,6 +61,7 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
child: ChequeManagementCardTile(
icon: Symbols.block_sharp,
label: AppLocalizations.of(context).stopCheque,
subtitle: AppLocalizations.of(context).stopChequeSubtitle,
onTap: () {
Navigator.push(
context,
@@ -74,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 {
final IconData icon;
final String label;
final String? subtitle;
final VoidCallback onTap;
final bool disable;
@@ -141,6 +109,7 @@ class ChequeManagementCardTile extends StatelessWidget {
super.key,
required this.icon,
required this.label,
this.subtitle,
required this.onTap,
this.disable = false,
});
@@ -183,6 +152,19 @@ class ChequeManagementCardTile extends StatelessWidget {
: theme.colorScheme.onSurface,
),
),
if (subtitle != null)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
subtitle!,
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium?.copyWith(
color: disable
? theme.disabledColor
: theme.colorScheme.onSurfaceVariant,
),
),
),
],
),
),

View File

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

View File

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

View File

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

View File

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

View File

@@ -309,7 +309,6 @@ class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
);
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'];
@@ -324,10 +323,6 @@ class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
}
_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 =

View File

@@ -278,7 +278,7 @@ class _StopSingleChequeScreenState extends State<StopSingleChequeScreen> {
);
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'];
@@ -293,10 +293,6 @@ class _StopSingleChequeScreenState extends State<StopSingleChequeScreen> {
}
_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 =

View File

@@ -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 '../../../l10n/app_localizations.dart';
import 'package:flutter/material.dart';
@@ -45,6 +43,7 @@ class _ServiceScreen extends State<ServiceScreen> {
disabled: false,
),
),
const SizedBox(height: 16),
Expanded(
child: ServiceManagementTile(
icon: Symbols.question_mark,
@@ -58,6 +57,7 @@ class _ServiceScreen extends State<ServiceScreen> {
disabled: false,
),
),
const SizedBox(height: 16),
Expanded(
child: ServiceManagementTile(
icon: Symbols.location_pin,
@@ -71,32 +71,6 @@ class _ServiceScreen extends State<ServiceScreen> {
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
],
),
@@ -148,8 +122,8 @@ class ServiceManagementTile extends StatelessWidget {
onTap:
disabled ? null : onTap, // Disable InkWell if the tile is disabled
borderRadius: BorderRadius.circular(12.0),
child: Center(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@@ -174,7 +148,6 @@ class ServiceManagementTile extends StatelessWidget {
),
),
),
),
);
}
}

View File

@@ -555,42 +555,5 @@
"stopChequeButton": "Stop Cheque",
"stopMultipleChequesTitle": "Stop Multiple Cheques",
"fromChequeNumberHint": "From Cheque Number *",
"toChequeNumberHint": "To Cheque Number *",
"failedToFetchChequeStatus": "Failed to fetch cheque status: {error}",
"revokeStopSubtitle": "Revoke your stopped cheques so as to reuse it",
"positivePaySubtitle": "Prevent unauthorized use of your issued cheque",
"positivePayTitle": "Positive Pay",
"pleaseEnterAccountNumber": "Please enter account number",
"chequeNumber": "Cheque Number",
"pleaseEnterChequeNumber": "Please enter cheque number",
"chequeIssuedDate": "Cheque Issued Date",
"pleaseSelectChequeIssuedDate": "Please select cheque issued date",
"payeeName": "Payee Name",
"pleaseEnterAmount": "Please enter the amount",
"processingData": "Processing Data",
"revokeStopCheque": "Revoke Stop Cheque",
"currentAccount": "Current Account",
"cashCreditAccount": "Cash Credit Account",
"revokeSingleStop": "Revoke Single Stop",
"noStoppedChequesPresent": "No stopped cheques present",
"revokeMultipleStops": "Revoke Multiple Stops",
"revokeSingleStopTitle": "Revoke Single Stop",
"chequeFound": "Cheque Found",
"chequeFixed": "Cheque Fixed",
"other": "Other",
"revokeIssueDate": "Revoke Issue Date",
"revokeExpiryDate": "Revoke Expiry Date",
"revokeAmount": "Revoke Amount",
"revokeComment": "Revoke Comment",
"otherReasons": "Other Reasons :",
"revokeStopButton": "Revoke Stop",
"invalidTpin": "Invalid TPIN",
"incorrectTpinMessage": "The TPIN you entered is incorrect. Please try again.",
"chequeAlreadyStopped": "The selected Cheque is already stopped",
"chequeAlreadyPresented": "The selected Cheque is already presented",
"chequeLost": "Cheque Lost",
"chequeStolen": "Cheque Stolen",
"chequeMissing": "Cheque Missing",
"chequeDamaged": "Cheque Damaged",
"close": "Close"
}
"toChequeNumberHint": "To Cheque Number *"
}

View File

@@ -556,42 +556,5 @@
"stopChequeButton": "चेक रोकें",
"stopMultipleChequesTitle": "एकाधिक चेक रोकें",
"fromChequeNumberHint": "चेक नंबर से *",
"toChequeNumberHint": "चेक नंबर तक *",
"failedToFetchChequeStatus": "चेक स्थिति लाने में विफल: {error}",
"revokeStopSubtitle": "अपने रोके गए चेकों को फिर से उपयोग करने के लिए निरस्त करें",
"positivePaySubtitle": "अनधिकृत उपयोग को रोकने के लिए अपने जारी किए गए चेक का विवरण जमा करें",
"positivePayTitle": "सकारात्मक वेतन",
"pleaseEnterAccountNumber": "कृपया खाता संख्या दर्ज करें",
"chequeNumber": "चेक संख्या",
"pleaseEnterChequeNumber": "कृपया चेक संख्या दर्ज करें",
"chequeIssuedDate": "चेक जारी करने की तारीख",
"pleaseSelectChequeIssuedDate": "कृपया चेक जारी करने की तारीख चुनें",
"payeeName": "प्राप्तकर्ता का नाम",
"pleaseEnterAmount": "कृपया राशि दर्ज करें",
"processingData": "डेटा संसाधित हो रहा है",
"revokeStopCheque": "चेक रोको रद्द करें",
"currentAccount": "चालू खाता",
"cashCreditAccount": "नगद श्रेय खाता",
"revokeSingleStop": "एकल रोक रद्द करें",
"noStoppedChequesPresent": "कोई रोका हुआ चेक मौजूद नहीं है",
"revokeMultipleStops": "कई रोक रद्द करें",
"revokeSingleStopTitle": "एकल रोक रद्द करें",
"chequeFound": "चेक मिला",
"chequeFixed": "चेक ठीक किया गया",
"other": "अन्य",
"revokeIssueDate": "रोक जारी करने की तारीख रद्द करें",
"revokeExpiryDate": "रोक समाप्ति तिथि रद्द करें",
"revokeAmount": "राशि रद्द करें",
"revokeComment": "टिप्पणी रद्द करें",
"otherReasons": "अन्य कारण :",
"revokeStopButton": "रोक रद्द करें",
"invalidTpin": "अमान्य टीपिन",
"incorrectTpinMessage": "आपके द्वारा दर्ज किया गया टीपिन गलत है। कृपया पुनः प्रयास करें।",
"chequeAlreadyStopped": "चुना हुआ चेक पहले से ही रोका हुआ है",
"chequeAlreadyPresented": "चुना हुआ चेक पहले से ही प्रस्तुत किया जा चुका है",
"chequeLost": "चेक खो गया",
"chequeStolen": "चेक चोरी हो गया",
"chequeMissing": "चेक गायब है",
"chequeDamaged": "चेक क्षतिग्रस्त है",
"close": "बंद करें"
}
"toChequeNumberHint": "चेक नंबर तक *"
}

View File

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

View File

@@ -773,6 +773,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.0"
send_message:
dependency: "direct main"
description:
name: send_message
sha256: "79b5f69fd3ab0b9e6265f8d972800d7989b3082a0523c7f4b8e38bf4e1c71235"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
share_plus:
dependency: "direct main"
description:
@@ -861,6 +869,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
simcards:
dependency: "direct main"
description:
name: simcards
sha256: b621cc265ebbb3e11009ca9be67063efbc011396c4224aff8b08edaba30fa5ae
url: "https://pub.dev"
source: hosted
version: "0.0.1"
sky_engine:
dependency: transitive
description: flutter
@@ -1003,7 +1019,7 @@ packages:
source: hosted
version: "3.1.4"
uuid:
dependency: transitive
dependency: "direct main"
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff

View File

@@ -34,7 +34,7 @@ dependencies:
cupertino_icons: ^1.0.6
jailbreak_root_detection: ^1.1.6
equatable: ^2.0.7
dio: ^5.8.0+1
dio: ^5.9.0
flutter_secure_storage: ^9.2.4
bloc: ^9.0.0
flutter_bloc: ^9.1.0
@@ -63,6 +63,9 @@ dependencies:
package_info_plus: ^4.2.0
flutter_local_notifications: ^19.5.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"