20 Commits

Author SHA1 Message Date
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
72a2c56392 Code Formatting 2025-12-05 16:02:49 +05:30
aef82237ac OTP added to daily limit #2 2025-12-05 15:59:59 +05:30
974f42bf95 OTP added to daily limit 2025-12-05 12:37:38 +05:30
4a8c69bb1e Account Info card changes and snackbar in statement #3 2025-12-04 16:50:08 +05:30
86aaaa1f6d Account Info card changes and snackbar in statement #2 2025-12-04 15:56:22 +05:30
6796793aac Account Info card changes and snackbar in statement 2025-12-04 15:40:24 +05:30
fbf6df7181 Icon and logo issues fixed 2025-12-04 12:44:39 +05:30
c7111d518a PDF Edited 2025-12-03 18:05:34 +05:30
5d307607fd Download notification created and Profile added in quick links 2025-12-02 17:06:49 +05:30
992092052a Customerf Info page changed and landing page changed 2025-12-02 13:31:40 +05:30
64fedabd89 Subtitles added in payment tabs with Localizations 2025-12-01 16:28:42 +05:30
4fc6f54fcd Subtitles added in payment tabs 2025-12-01 16:13:27 +05:30
8c7e94759a APK Build #1 2025-12-01 12:58:17 +05:30
8aa5b170ca Account Statement UI Changed 2025-11-28 12:22:51 +05:30
04a1ce26ec Profile Changed and Customer Info 2025-11-28 11:28:01 +05:30
52 changed files with 3936 additions and 848 deletions

View File

@@ -34,6 +34,7 @@ android {
ndkVersion "27.0.12077973"
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
@@ -80,4 +81,6 @@ flutter {
source '../..'
}
dependencies {}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
}

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
assets/images/profile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
assets/images/profile.svg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

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.contains("VERIFIED")) {
return; // Success
} else {
throw AuthException(message); // Throw message received
}
} else {
throw AuthException('Verification Failed');
}
} on DioException catch (e) {
if (kDebugMode) {
print(e.toString());
}
if (e.response?.statusCode == 401) {
throw AuthException(
e.response?.data['error'] ?? 'SOMETHING WENT WRONG');
}
throw NetworkException('Network error during verification');
} catch (e) {
throw UnexpectedException(
'Unexpected error during verification: ${e.toString()}');
}
}
Future<AuthToken> login(AuthCredentials credentials) async {
try {
final response = await _dio.post(

View File

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

View File

@@ -54,4 +54,34 @@ class LimitService {
throw Exception('Unexpected error: ${e.toString()}');
}
}
Future getOtpTLimit({
required String mobileNumber,
}) async {
final response = await _dio.post(
'/api/otp/send',
data: {'mobileNumber': mobileNumber, 'type': "TLIMIT"},
);
if (response.statusCode != 200) {
throw Exception("Invalid Mobile Number/Type");
}
print(response.toString());
return response.toString();
}
Future validateOtp({
required String otp,
required String mobileNumber,
}) async {
final response = await _dio.post(
'/api/otp/verify?mobileNumber=$mobileNumber',
data: {
'otp': otp,
},
);
if (response.statusCode != 200) {
throw Exception("Wrong OTP");
}
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

@@ -12,10 +12,8 @@ import 'package:kmobile/features/auth/controllers/theme_state.dart';
import 'config/routes.dart';
import 'di/injection.dart';
import 'features/auth/controllers/auth_cubit.dart';
import 'features/card/screens/card_management_screen.dart';
import 'features/accounts/screens/account_statement_screen.dart';
import 'package:kmobile/features/auth/controllers/auth_state.dart';
import 'features/auth/screens/login_screen.dart';
import 'features/service/screens/service_screen.dart';
import 'features/dashboard/screens/dashboard_screen.dart';
@@ -330,7 +328,6 @@ class _NavigationScaffoldState extends State<NavigationScaffold> {
return const Center(child: CircularProgressIndicator());
},
),
const CardManagementScreen(),
const ServiceScreen(),
];
@@ -392,10 +389,6 @@ class _NavigationScaffoldState extends State<NavigationScaffold> {
icon: const Icon(Icons.swap_vert_sharp),
label: AppLocalizations.of(context).transactions,
),
BottomNavigationBarItem(
icon: const Icon(Icons.credit_card),
label: AppLocalizations.of(context).card,
),
BottomNavigationBarItem(
icon: const Icon(Icons.miscellaneous_services),
label: AppLocalizations.of(context).services,

View File

@@ -15,7 +15,17 @@ class AppThemes {
colorScheme: colorScheme,
useMaterial3: true,
textTheme: GoogleFonts.rubikTextTheme(),
);
).copyWith(
appBarTheme: AppBarTheme(
backgroundColor: const Color(0xFF01A04C),
titleTextStyle: TextStyle(
color: colorScheme.onPrimary,
fontWeight: FontWeight.w700,
fontSize: 20,
),
iconTheme: IconThemeData(color: colorScheme.onPrimary),
actionsIconTheme: IconThemeData(color: colorScheme.onPrimary),
));
}
static ThemeData getDarkTheme(ThemeType type) {
@@ -32,7 +42,17 @@ class AppThemes {
textTheme: GoogleFonts.rubikTextTheme(
ThemeData(brightness: Brightness.dark).textTheme,
),
);
).copyWith(
appBarTheme: AppBarTheme(
backgroundColor: const Color(0xFF01A04C),
titleTextStyle: TextStyle(
color: colorScheme.onPrimary,
fontWeight: FontWeight.w700,
fontSize: 20,
),
iconTheme: IconThemeData(color: colorScheme.onPrimary),
actionsIconTheme: IconThemeData(color: colorScheme.onPrimary),
));
}
static Color _getSeedColor(ThemeType type) {

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/branch_service.dart';
import 'package:kmobile/api/services/cheque_service.dart';
import 'package:kmobile/api/services/limit_service.dart';
import 'package:kmobile/api/services/rtgs_service.dart';
import 'package:kmobile/api/services/neft_service.dart';
@@ -56,6 +57,7 @@ Future<void> setupDependencies() async {
getIt.registerSingleton<RtgsService>(RtgsService(getIt<Dio>()));
getIt.registerSingleton<ImpsService>(ImpsService(getIt<Dio>()));
getIt.registerSingleton<BranchService>(BranchService(getIt<Dio>()));
getIt.registerSingleton<ChequeService>(ChequeService(getIt<Dio>()));
getIt.registerLazySingleton<ChangePasswordService>(
() => ChangePasswordService(getIt<Dio>()),
);
@@ -74,9 +76,9 @@ Dio _createDioClient() {
final dio = Dio(
BaseOptions(
baseUrl:
'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com:8080', //test
//'http://lb-kccb-mobile-banking-app-848675342.ap-south-1.elb.amazonaws.com', //prod
//'https://kccbmbnk.net', //prod small
'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com', //test
//'http://lb-kccb-mobile-banking-app-848675342.ap-south-1.elb.amazonaws.com', //prod
//'https://kccbmbnk.net', //prod small
connectTimeout: const Duration(seconds: 60),
receiveTimeout: const Duration(seconds: 60),
headers: {

View File

@@ -42,10 +42,10 @@ class _AccountInfoScreen extends State<AccountInfoScreen> {
return AppLocalizations.of(context).recurringDeposit;
case 'ca':
return "Current Account";
case 'cc':
case 'cc':
return "Cash Credit Account";
case 'od':
return "Overdraft Account";
return "Overdraft Account";
default:
return AppLocalizations.of(context).unknownAccount;
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import '../../../l10n/app_localizations.dart';
class AllAccountsScreen extends StatefulWidget {
final List<User> users;
const AllAccountsScreen({super.key, required this.users});
@@ -19,19 +21,23 @@ class _AllAccountsScreenState extends State<AllAccountsScreen> {
if (accountType == null || accountType.isEmpty) return 'N/A';
switch (accountType.toLowerCase()) {
case 'sa':
return "Savings Account"; // Using hardcoded strings for simplicity
return AppLocalizations.of(context).savingsAccount;
case 'sb':
return "Savings Account";
return AppLocalizations.of(context).savingsAccount;
case 'ln':
return "Loan Account";
return AppLocalizations.of(context).loanAccount;
case 'td':
return "Term Deposit";
return AppLocalizations.of(context).termDeposit;
case 'rd':
return "Recurring Deposit";
return AppLocalizations.of(context).recurringDeposit;
case 'ca':
return "Current Account";
case 'cc':
return "Cash Credit Account";
case 'od':
return "Overdraft Account";
default:
return "Unknown Account";
return AppLocalizations.of(context).unknownAccount;
}
}
@@ -39,18 +45,25 @@ class _AllAccountsScreenState extends State<AllAccountsScreen> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('All Accounts'),
title: Text(AppLocalizations.of(context).viewall),
),
body: ListView.builder(
itemCount: widget.users.length,
itemBuilder: (context, index) {
final user = widget.users[index];
return Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: _buildAccountCard(user),
);
},
body: Column(
children: [
const SizedBox(height: 16.0), // Added space below the app bar
Expanded(
child: ListView.builder(
itemCount: widget.users.length,
itemBuilder: (context, index) {
final user = widget.users[index];
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 8.0),
child: _buildAccountCard(user),
);
},
),
), // Closing Expanded
], // Closing Column
),
);
}

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,12 +31,23 @@ class LoginScreenState extends State<LoginScreen>
super.dispose();
}
void _submitForm() {
void _submitForm() async {
if (_formKey.currentState!.validate()) {
context.read<AuthCubit>().login(
_customerNumberController.text.trim(),
_passwordController.text,
);
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,
);
}
}
}

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,165 @@
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(() {
_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

@@ -147,7 +147,9 @@ class _ManageBeneficiariesScreen extends State<ManageBeneficiariesScreen> {
child: TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: "Search by name or account number",
hintText:
AppLocalizations.of(context).searchByNameOrAccountHint,
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),

View File

@@ -56,8 +56,6 @@ class _BlockCardScreen extends State<BlockCardScreen> {
appBar: AppBar(
title: Text(
AppLocalizations.of(context).blockCard,
style:
const TextStyle(color: Colors.black, fontWeight: FontWeight.w500),
),
centerTitle: false,
),

View File

@@ -46,8 +46,6 @@ class _CardPinChangeDetailsScreen extends State<CardPinChangeDetailsScreen> {
appBar: AppBar(
title: Text(
AppLocalizations.of(context).cardDetails,
style:
const TextStyle(color: Colors.black, fontWeight: FontWeight.w500),
),
centerTitle: false,
),

View File

@@ -46,8 +46,6 @@ class _CardPinSetScreen extends State<CardPinSetScreen> {
appBar: AppBar(
title: Text(
AppLocalizations.of(context).cardPin,
style:
const TextStyle(color: Colors.black, fontWeight: FontWeight.w500),
),
centerTitle: false,
),

View File

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

View File

@@ -1,76 +1,82 @@
import 'package:flutter/material.dart';
import 'package:kmobile/features/enquiry/screens/enquiry_screen.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/features/cheque/screens/cheque_enquiry_screen.dart';
import 'package:kmobile/features/cheque/screens/stop_cheque_screen.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import '../../../l10n/app_localizations.dart';
class ChequeManagementScreen extends StatefulWidget {
const ChequeManagementScreen({super.key});
final List<User> users;
final int selectedIndex;
const ChequeManagementScreen({
super.key,
required this.users,
required this.selectedIndex,
});
@override
State<ChequeManagementScreen> createState() => _ChequeManagementScreen();
}
class _ChequeManagementScreen extends State<ChequeManagementScreen> {
List<User> get users => widget.users;
int get selectedAccountIndex => widget.selectedIndex;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
AppLocalizations.of(context).chequeManagement,
style: TextStyle(
color: Theme.of(context).textTheme.titleLarge?.color,
fontWeight: FontWeight.w500),
),
centerTitle: false,
),
body: Stack(
children: [
ListView(
children: [
const SizedBox(height: 15),
ChequeManagementTile(
icon: Symbols.add,
label: AppLocalizations.of(context).requestChequeBook,
onTap: () {},
),
Divider(height: 1, color: Theme.of(context).dividerColor),
ChequeManagementTile(
icon: Symbols.data_alert,
label: AppLocalizations.of(context).enquiry,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const EnquiryScreen()),
);
},
),
Divider(height: 1, color: Theme.of(context).dividerColor),
ChequeManagementTile(
icon: Symbols.approval_delegation,
label: AppLocalizations.of(context).chequeDeposit,
onTap: () {},
),
Divider(height: 1, color: Theme.of(context).dividerColor),
ChequeManagementTile(
icon: Symbols.front_hand,
label: AppLocalizations.of(context).stopCheque,
onTap: () {},
),
Divider(height: 1, color: Theme.of(context).dividerColor),
ChequeManagementTile(
icon: Symbols.cancel_presentation,
label: AppLocalizations.of(context).revokeStop,
onTap: () {},
),
Divider(height: 1, color: Theme.of(context).dividerColor),
ChequeManagementTile(
icon: Symbols.payments,
label: AppLocalizations.of(context).positivePay,
onTap: () {},
),
Divider(height: 1, color: Theme.of(context).dividerColor),
],
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: ChequeManagementCardTile(
icon: Symbols.payments,
label: AppLocalizations.of(context).chequeEnquiryTitle,
subtitle:
AppLocalizations.of(context).chequeEnquirySubtitle,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChequeEnquiryScreen(
users: users,
selectedIndex: selectedAccountIndex,
),
),
);
},
),
),
Expanded(
child: ChequeManagementCardTile(
icon: Symbols.block_sharp,
label: AppLocalizations.of(context).stopCheque,
subtitle: AppLocalizations.of(context).stopChequeSubtitle,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StopChequeScreen(
users: users,
selectedIndex: selectedAccountIndex,
),
),
);
},
),
),
],
),
),
IgnorePointer(
child: Center(
@@ -92,25 +98,79 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
}
}
class ChequeManagementTile extends StatelessWidget {
class ChequeManagementCardTile extends StatelessWidget {
final IconData icon;
final String label;
final String? subtitle;
final VoidCallback onTap;
final bool disable;
const ChequeManagementTile({
const ChequeManagementCardTile({
super.key,
required this.icon,
required this.label,
this.subtitle,
required this.onTap,
this.disable = false,
});
@override
Widget build(BuildContext context) {
return ListTile(
leading: Icon(icon),
title: Text(label),
trailing: const Icon(Symbols.arrow_right, size: 20),
onTap: onTap,
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, // Add some elevation for better visual separation
child: InkWell(
onTap:
disable ? null : onTap, // Disable InkWell if the tile is disabled
borderRadius: BorderRadius.circular(12.0),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 16.0),
child: Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
size: 48, // Make icon larger
color: disable
? theme.disabledColor
: theme.colorScheme.primary,
),
const SizedBox(height: 12),
Text(
label,
textAlign: TextAlign.center,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: disable
? theme.disabledColor
: theme.colorScheme.onSurface,
),
),
if (subtitle != null)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
subtitle!,
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium?.copyWith(
color: disable
? theme.disabledColor
: theme.colorScheme.onSurfaceVariant,
),
),
),
],
),
),
),
),
),
);
}
}

View File

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

View File

@@ -0,0 +1,287 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/cheque_service.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/features/fund_transfer/screens/transaction_pin_screen.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class StopMultipleChequesScreen extends StatefulWidget {
final User selectedAccount;
final String date;
final String instrType;
final String fromCheque;
final String toCheque;
const StopMultipleChequesScreen(
{super.key,
required this.selectedAccount,
required this.date,
required this.instrType,
required this.fromCheque,
required this.toCheque});
@override
State<StopMultipleChequesScreen> createState() =>
_StopMultipleChequesScreenState();
}
class _StopMultipleChequesScreenState extends State<StopMultipleChequesScreen> {
final _formKey = GlobalKey<FormState>();
final _stopFromChequeNoController = TextEditingController();
final _stopToChequeNoController = TextEditingController();
final _stopIssueDateController = TextEditingController();
final _stopExpiryDateController = TextEditingController();
final _stopAmountController = TextEditingController();
final _stopCommentController = TextEditingController();
final _chequeService = getIt<ChequeService>();
String _formatDate(String dateString) {
if (dateString.length != 8) {
return dateString; // Return as is if not in expected ddmmyyyy format
}
try {
final day = dateString.substring(0, 2);
final month = dateString.substring(2, 4);
final year = dateString.substring(4, 8);
return '$day/$month/$year';
} catch (e) {
return dateString; // Return original string on error
}
}
Future<void> _showResponseDialog(String title, String message) async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text(title),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text(message),
],
),
),
actions: <Widget>[
TextButton(
child: const Text('Close'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context).stopMultipleChequesTitle),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: ListView(
children: [
Card(
elevation: 0,
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: ListTile(
leading: Image.asset(
'assets/images/logo.png',
width: 40,
height: 40,
),
title: Text(widget.selectedAccount.accountNo!),
subtitle:
Text(AppLocalizations.of(context).accountNumberTitle),
),
),
const SizedBox(height: 24),
TextFormField(
controller: _stopFromChequeNoController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).fromChequeNumberHint,
border: const OutlineInputBorder(),
),
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(),
),
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,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).stopIssueDateHint,
border: const OutlineInputBorder(),
),
keyboardType: TextInputType.datetime,
),
const SizedBox(height: 16),
TextFormField(
controller: _stopExpiryDateController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).stopExpiryDateHint,
border: const OutlineInputBorder(),
),
keyboardType: TextInputType.datetime,
),
const SizedBox(height: 16),
TextFormField(
controller: _stopAmountController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).stopAmountHint,
border: const OutlineInputBorder(),
),
keyboardType: TextInputType.number,
),
const SizedBox(height: 16),
TextFormField(
controller: _stopCommentController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).stopCommentHint,
border: const OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextFormField(
initialValue: _formatDate(widget.date),
readOnly: true,
decoration: InputDecoration(
labelText:
AppLocalizations.of(context).chequebookIssueDateHint,
border: const OutlineInputBorder(),
),
),
const SizedBox(height: 32),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TransactionPinScreen(
onPinCompleted: (ctx, pin) async {
Navigator.pop(context);
try {
final response = await _chequeService.stopCheque(
accountno: widget.selectedAccount.accountNo!,
stopFromChequeNo:
_stopFromChequeNoController.text,
instrType: widget.instrType,
stopToChequeNo: _stopToChequeNoController.text,
stopIssueDate: _stopIssueDateController.text,
stopExpiryDate: _stopExpiryDateController.text,
stopAmount: _stopAmountController.text,
stopComment: _stopCommentController.text,
chequeIssueDate: widget.date,
tpin: pin,
);
if (!mounted) return;
final decodedResponse = jsonDecode(response);
final status = decodedResponse['status'];
final message = decodedResponse['message'];
if (status == 'SUCCESS') {
_showResponseDialog('Success', message);
} else {
_showResponseDialog('Error', message);
}
} on Exception catch (e) {
print('inside catch block');
print(e.toString());
try {
final errorBodyString =
e.toString().split('Exception: ')[1];
final errorBody = jsonDecode(errorBodyString);
if (errorBody.containsKey('error') &&
errorBody['error'] == 'INCORRECT_TPIN') {
_showResponseDialog('Invalid TPIN',
'The TPIN you entered is incorrect. Please try again.');
} else {
_showResponseDialog(
'Error', 'Internal Server Error');
}
} catch (_) {
_showResponseDialog(
'Error', 'Internal Server Error');
}
}
},
),
),
);
}
},
child: Text(AppLocalizations.of(context).stopChequeButton),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,256 @@
import 'dart:convert';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/di/injection.dart';
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/cheque_service.dart';
import 'package:kmobile/features/fund_transfer/screens/transaction_pin_screen.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class StopSingleChequeScreen extends StatefulWidget {
final User selectedAccount;
final String date;
final String instrType;
final String fromCheque;
final String toCheque;
const StopSingleChequeScreen(
{super.key,
required this.selectedAccount,
required this.date,
required this.instrType,
required this.fromCheque,
required this.toCheque});
@override
State<StopSingleChequeScreen> createState() => _StopSingleChequeScreenState();
}
class _StopSingleChequeScreenState extends State<StopSingleChequeScreen> {
final _formKey = GlobalKey<FormState>();
final _stopFromChequeNoController = TextEditingController();
final _stopIssueDateController = TextEditingController();
final _stopExpiryDateController = TextEditingController();
final _stopAmountController = TextEditingController();
final _stopCommentController = TextEditingController();
final _chequeService = getIt<ChequeService>();
String _formatDate(String dateString) {
if (dateString.length != 8) {
return dateString; // Return as is if not in expected ddmmyyyy format
}
try {
final day = dateString.substring(0, 2);
final month = dateString.substring(2, 4);
final year = dateString.substring(4, 8);
return '$day/$month/$year';
} catch (e) {
return dateString; // Return original string on error
}
}
Future<void> _showResponseDialog(String title, String message) async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text(title),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text(message),
],
),
),
actions: <Widget>[
TextButton(
child: Text(AppLocalizations.of(context).closeButton),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context).stopSingleChequeTitle),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: ListView(
children: [
Card(
elevation: 0,
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: ListTile(
leading: Image.asset(
'assets/images/logo.png',
width: 40,
height: 40,
),
title: Text(widget.selectedAccount.accountNo!),
subtitle:
Text(AppLocalizations.of(context).accountNumberLabel),
),
),
const SizedBox(height: 24),
TextFormField(
controller: _stopFromChequeNoController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).chequeNumberLabel,
border: OutlineInputBorder(),
),
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,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).stopIssueDateLabel,
border: const OutlineInputBorder(),
),
keyboardType: TextInputType.datetime,
),
const SizedBox(height: 16),
TextFormField(
controller: _stopExpiryDateController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).stopExpiryDateLabel,
border: const OutlineInputBorder(),
),
keyboardType: TextInputType.datetime,
),
const SizedBox(height: 16),
TextFormField(
controller: _stopAmountController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).stopAmountHint,
border: const OutlineInputBorder(),
),
keyboardType: TextInputType.number,
),
const SizedBox(height: 16),
TextFormField(
controller: _stopCommentController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).stopCommentHint,
border: const OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextFormField(
initialValue: _formatDate(widget.date),
readOnly: true,
decoration: InputDecoration(
labelText:
AppLocalizations.of(context).chequebookIssueDateHint,
border: const OutlineInputBorder(),
),
),
const SizedBox(height: 32),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TransactionPinScreen(
onPinCompleted: (ctx, pin) async {
Navigator.pop(context);
try {
final response = await _chequeService.stopCheque(
accountno: widget.selectedAccount.accountNo!,
stopFromChequeNo:
_stopFromChequeNoController.text,
instrType: widget.instrType,
stopToChequeNo:
_stopFromChequeNoController.text,
stopIssueDate: _stopIssueDateController.text,
stopExpiryDate: _stopExpiryDateController.text,
stopAmount: _stopAmountController.text,
stopComment: _stopCommentController.text,
chequeIssueDate: widget.date,
tpin: pin,
);
if (!mounted) return;
final decodedResponse = jsonDecode(response);
final status = decodedResponse['status'];
final message = decodedResponse['message'];
if (status == 'SUCCESS') {
_showResponseDialog('Success', message);
} else {
_showResponseDialog('Error', message);
}
} on Exception catch (e) {
print('inside catch block');
print(e.toString());
try {
final errorBodyString =
e.toString().split('Exception: ')[1];
final errorBody = jsonDecode(errorBodyString);
if (errorBody.containsKey('error') &&
errorBody['error'] == 'INCORRECT_TPIN') {
_showResponseDialog('Invalid TPIN',
'The TPIN you entered is incorrect. Please try again.');
} else {
_showResponseDialog(
'Error', 'Internal Server Error');
}
} catch (_) {
_showResponseDialog(
'Error', 'Internal Server Error');
}
}
},
),
),
);
}
},
child: Text(AppLocalizations.of(context).stopChequeButton),
),
],
),
),
),
);
}
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import '../../../l10n/app_localizations.dart';
class CustomerInfoScreen extends StatefulWidget {
@@ -13,6 +14,7 @@ class CustomerInfoScreen extends StatefulWidget {
class _CustomerInfoScreenState extends State<CustomerInfoScreen> {
late final User user = widget.user;
int _selectedCard = 0; // 0 for Personal Info, 1 for KYC
String _maskPrimaryId(String? primaryId) {
if (primaryId == null || primaryId.length <= 4) {
@@ -26,100 +28,207 @@ class _CustomerInfoScreenState extends State<CustomerInfoScreen> {
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: Text(
AppLocalizations.of(context)
.customerInfo
.replaceFirst(RegExp('\n'), ''),
appBar: AppBar(
title: Text(
AppLocalizations.of(context)
.customerInfo
.replaceFirst(RegExp('\n'), ''),
),
),
),
body: Stack(
children: [
SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: SafeArea(
child: Center(
body: SafeArea(
child: Stack(
children: [
SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const SizedBox(height: 30),
CircleAvatar(
radius: 50,
child: SvgPicture.asset(
'assets/images/avatar_male.svg',
width: 150,
height: 150,
fit: BoxFit.cover,
Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(
color: theme.colorScheme.outline.withOpacity(0.2),
width: 1,
),
),
),
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Text(
user.name ?? '',
style: TextStyle(
fontSize: 20,
color: theme.colorScheme.onSurface,
fontWeight: FontWeight.w500,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
const SizedBox(
width: 56,
height: 56,
child: CircleAvatar(
radius: 50,
child: Icon(
Symbols.person,
size: 56,
),
),
),
const SizedBox(width: 12),
// Name + mobile
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
// If you want to show the user's name instead, replace below.
user.name ?? '',
style:
theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 4),
Text(
user.cifNumber ?? '',
style:
theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface
.withOpacity(0.7),
),
),
],
),
),
],
),
),
),
Text(
'${AppLocalizations.of(context).cif}: ${user.cifNumber ?? 'N/A'}',
style: TextStyle(
fontSize: 16,
color: theme.colorScheme.onSurfaceVariant),
const SizedBox(height: 16),
// Toggle Buttons for Personal Info and KYC
SizedBox(
width: double.infinity,
child: CupertinoSlidingSegmentedControl<int>(
groupValue: _selectedCard,
thumbColor: Theme.of(context)
.colorScheme
.onPrimary, // Set selected switch color to theme primary color
onValueChanged: (int? newValue) {
if (newValue != null) {
setState(() {
_selectedCard = newValue;
});
}
},
children: {
0: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 10),
child: Text(
AppLocalizations.of(context).personaldetails),
),
1: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 10),
child:
Text(AppLocalizations.of(context).kycdetails),
),
},
),
),
const SizedBox(height: 30),
InfoField(
label: AppLocalizations.of(context).activeAccounts,
value: user.activeAccounts?.toString() ?? 'N/A',
const SizedBox(height: 16),
// Card that shows content based on the toggle
Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(
color: theme.colorScheme.outline.withOpacity(0.2),
width: 1,
),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: _selectedCard == 0
? _buildPersonalInfo(theme)
: _buildKycDetails(theme),
),
),
),
InfoField(
label: AppLocalizations.of(context).mobileNumber,
value: user.mobileNo ?? 'N/A',
),
InfoField(
label: AppLocalizations.of(context).dateOfBirth,
value: (user.dateOfBirth != null &&
user.dateOfBirth!.length == 8)
? '${user.dateOfBirth!.substring(0, 2)}-${user.dateOfBirth!.substring(2, 4)}-${user.dateOfBirth!.substring(4, 8)}'
: 'N/A',
), // Replace with DOB if available
InfoField(
label: AppLocalizations.of(context).branchCode,
value: user.branchId ?? 'N/A',
),
InfoField(
label: AppLocalizations.of(context).address,
value: user.address ?? 'N/A',
), // Replace with Aadhar if available
InfoField(
label: AppLocalizations.of(context).primaryId,
value: _maskPrimaryId(user.primaryId),
), // Replace with PAN if available
],
),
),
),
),
),
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
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 _buildPersonalInfo(ThemeData theme) {
return Column(
key: const ValueKey('personal_info'),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppLocalizations.of(context).personaldetails,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 16),
InfoField(
label: AppLocalizations.of(context).activeAccounts,
value: user.activeAccounts?.toString() ?? 'N/A',
),
InfoField(
label: AppLocalizations.of(context).mobileNumber,
value: user.mobileNo ?? 'N/A',
),
InfoField(
label: AppLocalizations.of(context).dateOfBirth,
value: (user.dateOfBirth != null && user.dateOfBirth!.length == 8)
? '${user.dateOfBirth!.substring(0, 2)}-${user.dateOfBirth!.substring(2, 4)}-${user.dateOfBirth!.substring(4, 8)}'
: 'N/A',
),
InfoField(
label: AppLocalizations.of(context).branchCode,
value: user.branchId ?? 'N/A',
),
InfoField(
label: AppLocalizations.of(context).address,
value: user.address ?? 'N/A',
),
],
);
}
Widget _buildKycDetails(ThemeData theme) {
return Column(
key: const ValueKey('kyc_details'),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppLocalizations.of(context).kycdetails,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 16),
InfoField(
label: AppLocalizations.of(context).primaryId,
value: _maskPrimaryId(user.primaryId),
),
],
);
}
}
@@ -141,16 +250,16 @@ class InfoField extends StatelessWidget {
children: [
Text(
label,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
color: theme.colorScheme.onSurfaceVariant,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.6),
),
),
const SizedBox(height: 3),
const SizedBox(height: 4),
Text(
value,
style: TextStyle(fontSize: 16, color: theme.colorScheme.onSurface),
value.isEmpty ? 'N/A' : value,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
],
),

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/features/accounts/screens/account_info_screen.dart';
@@ -8,8 +8,8 @@ import 'package:kmobile/features/accounts/screens/account_statement_screen.dart'
import 'package:kmobile/features/accounts/screens/all_accounts_screen.dart';
import 'package:kmobile/features/auth/controllers/auth_cubit.dart';
import 'package:kmobile/features/auth/controllers/auth_state.dart';
import 'package:kmobile/features/customer_info/screens/customer_info_screen.dart';
import 'package:kmobile/features/cheque/screens/cheque_management_screen.dart';
import 'package:kmobile/features/customer_info/screens/customer_info_screen.dart';
import 'package:kmobile/features/beneficiaries/screens/manage_beneficiaries_screen.dart';
import 'package:kmobile/features/enquiry/screens/enquiry_screen.dart';
import 'package:kmobile/features/fund_transfer/screens/fund_transfer_screen.dart';
@@ -65,68 +65,24 @@ class _DashboardScreenState extends State<DashboardScreen>
});
}
Widget _buildViewAllTab(List<User> users) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AllAccountsScreen(users: users),
),
);
},
child: Container(
width: 40, // Small width for the tab
height: 160,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant,
),
child: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"View",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
Text(
"All",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
Icon(
Icons.arrow_forward_ios,
size: 16,
),
],
),
),
);
}
Widget _buildAccountCard(User user, bool isSelected) {
final theme = Theme.of(context);
final bool isCardVisible = _visibilityMap[user.accountNo] ?? false;
// Animated scale for the selected card
final scale = isSelected ? 1.0 : 0.85;
return AnimatedScale(
duration: const Duration(milliseconds: 200),
scale: scale,
child: Transform.translate(
offset: isSelected ? const Offset(10.0, 0.0) : Offset.zero,
final scale = isSelected ? 1.02 : 0.9;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 3.0),
child: AnimatedScale(
duration: const Duration(milliseconds: 200),
scale: scale,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 2),
padding: const EdgeInsets.symmetric(
horizontal: 18,
vertical: 10,
),
decoration: BoxDecoration(
color: const Color(0xFF01A04C),
borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(20),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -337,7 +293,7 @@ class _DashboardScreenState extends State<DashboardScreen>
case 'cc':
return "Cash Credit Account";
case 'od':
return "Overdraft Account";
return "Overdraft Account";
default:
return AppLocalizations.of(context).unknownAccount;
}
@@ -388,6 +344,19 @@ class _DashboardScreenState extends State<DashboardScreen>
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final authState = context.read<AuthCubit>().state;
String mobileNumberToPass = '';
String customerNo = '';
String customerName = '';
if (authState is Authenticated) {
if (selectedAccountIndex >= 0 &&
selectedAccountIndex < authState.users.length) {
mobileNumberToPass =
authState.users[selectedAccountIndex].mobileNo ?? '';
customerNo = authState.users[selectedAccountIndex].cifNumber ?? '';
customerName = authState.users[selectedAccountIndex].name ?? '';
}
}
return BlocListener<AuthCubit, AuthState>(
listener: (context, state) async {
if (state is Authenticated && !_biometricPromptShown) {
@@ -402,59 +371,67 @@ class _DashboardScreenState extends State<DashboardScreen>
child: Scaffold(
backgroundColor: theme.scaffoldBackgroundColor,
appBar: AppBar(
backgroundColor: theme.scaffoldBackgroundColor,
leading: Padding(
padding: const EdgeInsets.only(left: 10.0),
child: InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () {
final authState = context.read<AuthCubit>().state;
String mobileNumberToPass = '';
String customerNo = '';
String customerName = '';
if (authState is Authenticated) {
if (selectedAccountIndex >= 0 &&
selectedAccountIndex < authState.users.length) {
mobileNumberToPass =
authState.users[selectedAccountIndex].mobileNo ?? '';
customerNo =
authState.users[selectedAccountIndex].cifNumber ?? '';
customerName =
authState.users[selectedAccountIndex].name ?? '';
}
}
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProfileScreen(
mobileNumber: mobileNumberToPass,
customerNo: customerNo,
customerName: customerName),
),
);
},
child: CircleAvatar(
backgroundColor: Colors.grey[200],
radius: 20,
child: SvgPicture.asset(
'assets/images/avatar_male.svg',
width: 40,
height: 40,
fit: BoxFit.cover,
child: Material(
elevation: 4.0,
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder(
side: const BorderSide(color: Colors.white, width: 1.5),
borderRadius: BorderRadius.circular(12.0),
),
child: Container(
width: 40,
height: 40,
decoration: const BoxDecoration(
color: Colors.white,
),
child: Image.asset(
'assets/images/logo.png',
fit: BoxFit.fill,
),
),
),
),
title: Text(
AppLocalizations.of(context).kccbMobile,
textAlign: TextAlign.left,
AppLocalizations.of(context).kccBankFull.replaceAll('-', '\u2011'),
textAlign: TextAlign.center,
softWrap: true, // Explicitly allow wrapping
maxLines: 2, // Allow text to wrap to a maximum of 2 lines
style: TextStyle(
color: theme.colorScheme.primary,
color: theme.colorScheme.onPrimary,
fontWeight: FontWeight.w700,
fontSize: 20,
),
),
centerTitle: true,
// Removed centerTitle: true to give more space for text wrapping
actions: [
Padding(
padding: const EdgeInsets.only(right: 10.0),
child: InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProfileScreen(
mobileNumber: mobileNumberToPass,
customerNo: customerNo,
customerName: customerName),
),
);
},
child: const CircleAvatar(
radius: 21,
child: Icon(
Symbols.person,
size: 30,
),
),
),
),
],
),
body: BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) {
@@ -467,16 +444,16 @@ class _DashboardScreenState extends State<DashboardScreen>
final accountType = currAccount.accountType?.toLowerCase();
final isPaymentDisabled = accountType != 'sa' &&
accountType != 'sb' &&
accountType != 'ca';
accountType != 'ca' &&
accountType != 'cc';
// firsttime load
if (!_txInitialized) {
_txInitialized = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
});
WidgetsBinding.instance.addPostFrameCallback((_) {});
}
_pageController ??= PageController(
initialPage: selectedAccountIndex,
viewportFraction: 0.80,
viewportFraction: 0.75,
);
final firstName = getProcessedFirstName(currAccount.name);
@@ -486,8 +463,9 @@ class _DashboardScreenState extends State<DashboardScreen>
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 16), // Added spacing
Padding(
padding: const EdgeInsets.only(left: 8.0),
padding: const EdgeInsets.only(left: 4.0),
child: Text(
"${AppLocalizations.of(context).hi} $firstName!",
style: GoogleFonts.baumans().copyWith(
@@ -500,59 +478,60 @@ class _DashboardScreenState extends State<DashboardScreen>
const SizedBox(height: 16),
// Account Info Cards
ClipRect(
child: SizedBox(
// This SizedBox defines the height for the Stack
height: 160,
child: Stack(
children: [
// PageView part, painted underneath
Padding(
padding: const EdgeInsets.only(
left: 48.0), // Space for tab (40) + gap (8)
child: SizedBox(
// Keep SizedBox for PageView height
height: 160,
child: PageView.builder(
controller: _pageController,
itemCount: users.length,
clipBehavior: Clip
.none, // Keep this to show adjacent cards
padEnds: false,
onPageChanged: (int newIndex) async {
if (newIndex == selectedAccountIndex)
return;
SizedBox(
height: 160,
child: PageView.builder(
clipBehavior: Clip.none,
controller: _pageController,
itemCount:
users.length, // Keep this to show adjacent cards
// Hide the balance of the old card when scrolling away
final oldAccountNo =
users[selectedAccountIndex].accountNo;
if (oldAccountNo != null) {
_visibilityMap[oldAccountNo] = false;
}
onPageChanged: (int newIndex) async {
if (newIndex == selectedAccountIndex) return;
setState(() {
selectedAccountIndex = newIndex;
});
},
itemBuilder: (context, index) {
final user = users[index];
final isSelected =
index == selectedAccountIndex;
return _buildAccountCard(
user, isSelected);
},
),
),
),
// View All tab part, painted on top
Align(
alignment: Alignment.centerLeft,
child: _buildViewAllTab(users),
),
],
),
// Hide the balance of the old card when scrolling away
final oldAccountNo =
users[selectedAccountIndex].accountNo;
if (oldAccountNo != null) {
_visibilityMap[oldAccountNo] = false;
}
setState(() {
selectedAccountIndex = newIndex;
});
},
itemBuilder: (context, index) {
final user = users[index];
final isSelected = index == selectedAccountIndex;
return _buildAccountCard(user, isSelected);
},
),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
AllAccountsScreen(users: users),
),
);
},
child: Text(
AppLocalizations.of(context).viewall,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
),
),
),
],
),
const SizedBox(height: 18),
Text(
AppLocalizations.of(context).quickLinks,
@@ -639,8 +618,8 @@ class _DashboardScreenState extends State<DashboardScreen>
selectedIndex: selectedAccountIndex,
)));
}),
_buildQuickLink(Icons.location_pin, "Branch Locator",
() {
_buildQuickLink(Icons.location_pin,
AppLocalizations.of(context).branchlocator, () {
Navigator.push(
context,
MaterialPageRoute(
@@ -666,18 +645,19 @@ class _DashboardScreenState extends State<DashboardScreen>
const EnquiryScreen()));
}),
_buildQuickLink(
Symbols.request_quote,
Symbols.checkbook,
AppLocalizations.of(context).chequeManagement,
() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const ChequeManagementScreen(),
builder: (context) => ChequeManagementScreen(
users: users,
selectedIndex: selectedAccountIndex),
),
);
},
disable: true,
disable: isPaymentDisabled,
),
],
),
@@ -695,32 +675,6 @@ class _DashboardScreenState extends State<DashboardScreen>
);
}
List<Widget> _buildTransactionShimmer() {
final theme = Theme.of(context);
return List.generate(3, (i) {
return ListTile(
leading: Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: CircleAvatar(
radius: 12, backgroundColor: theme.scaffoldBackgroundColor),
),
title: Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(
height: 10, width: 100, color: theme.scaffoldBackgroundColor),
),
subtitle: Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(
height: 8, width: 60, color: theme.scaffoldBackgroundColor),
),
);
});
}
Widget _buildQuickLink(
IconData icon,
String label,

View File

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

View File

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

View File

@@ -198,7 +198,8 @@ class _FundTransferBeneficiaryScreenState
child: TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: "Search by name or account number",
hintText:
AppLocalizations.of(context).searchByNameOrAccountHint,
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),

View File

@@ -42,7 +42,9 @@ class FundTransferScreen extends StatelessWidget {
Expanded(
child: FundTransferManagementTile(
icon: Symbols.person,
label: "Self Pay",
label: AppLocalizations.of(context).selfPay,
subtitle:
AppLocalizations.of(context).ftselfpaysubtitle,
onTap: () {
Navigator.push(
context,
@@ -64,6 +66,7 @@ class FundTransferScreen extends StatelessWidget {
child: FundTransferManagementTile(
icon: Symbols.input_circle,
label: AppLocalizations.of(context).ownBank,
subtitle: AppLocalizations.of(context).ftownsubtitle,
onTap: () {
Navigator.push(
context,
@@ -84,6 +87,8 @@ class FundTransferScreen extends StatelessWidget {
child: FundTransferManagementTile(
icon: Symbols.output_circle,
label: AppLocalizations.of(context).outsideBank,
subtitle:
AppLocalizations.of(context).ftoutsidesubtitle,
onTap: () {
Navigator.push(
context,
@@ -127,6 +132,7 @@ class FundTransferScreen extends StatelessWidget {
class FundTransferManagementTile extends StatelessWidget {
final IconData icon;
final String label;
final String? subtitle;
final VoidCallback onTap;
final bool disable;
@@ -134,6 +140,7 @@ class FundTransferManagementTile extends StatelessWidget {
super.key,
required this.icon,
required this.label,
this.subtitle,
required this.onTap,
this.disable = false,
});
@@ -174,6 +181,19 @@ class FundTransferManagementTile 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,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/features/fund_transfer/screens/fund_transfer_self_amount_screen.dart';
import 'package:kmobile/l10n/app_localizations.dart';
import 'package:kmobile/widgets/bank_logos.dart';
class FundTransferSelfAccountsScreen extends StatelessWidget {
@@ -43,7 +44,7 @@ class FundTransferSelfAccountsScreen extends StatelessWidget {
return Scaffold(
appBar: AppBar(
title: const Text("Select Account"),
title: Text(AppLocalizations.of(context).selectAccount),
),
body: Stack(
children: [

View File

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

View File

@@ -0,0 +1,120 @@
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/limit_service.dart';
import '../../../di/injection.dart';
import '../../../l10n/app_localizations.dart';
class ChangeLimitOTPScreen extends StatefulWidget {
final String newLimit;
final String mobileNumber;
// ignore: use_key_in_widget_constructors
const ChangeLimitOTPScreen({
required this.newLimit,
required this.mobileNumber,
});
@override
State<ChangeLimitOTPScreen> createState() => _ChangeLimitOTPScreenState();
}
class _ChangeLimitOTPScreenState extends State<ChangeLimitOTPScreen> {
bool _isLoading = true;
final TextEditingController otpController = TextEditingController();
@override
void initState() {
super.initState();
// Simulate OTP sending delay
Future.delayed(const Duration(seconds: 2), () {
setState(() {
_isLoading = false;
});
});
}
final limitService = getIt<LimitService>();
Future<void> _validateOTP() async {
try {
await limitService.validateOtp(
otp: otpController.text,
mobileNumber: widget.mobileNumber,
);
// If OTP is valid, then change the limit
limitService.editLimit(
double.parse(widget.newLimit),
);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text("Limit has been Changed"),
));
// Navigate back to profile or login
Navigator.of(context).popUntil((route) => route.isFirst);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(AppLocalizations.of(context).invalidOtp)),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(AppLocalizations.of(context).otpVerification)),
body: Stack(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: _isLoading
? const Center(child: CircularProgressIndicator())
: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
AppLocalizations.of(context).otpSent,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 24),
TextFormField(
controller: otpController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).enterOTP,
border: const OutlineInputBorder(),
),
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _validateOTP,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
child: Text(AppLocalizations.of(context).validateOTP),
),
),
],
),
),
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
),
),
),
),
),
],
),
);
}
}

View File

@@ -2,11 +2,13 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:kmobile/api/services/limit_service.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/features/profile/change_limit_otp_screen.dart';
import 'package:kmobile/l10n/app_localizations.dart';
import 'package:intl/intl.dart';
class DailyLimitScreen extends StatefulWidget {
const DailyLimitScreen({super.key});
final String mobileNumber;
const DailyLimitScreen({super.key, required this.mobileNumber});
@override
State<DailyLimitScreen> createState() => _DailyLimitScreenState();
}
@@ -74,22 +76,40 @@ class _DailyLimitScreenState extends State<DailyLimitScreen> {
child: Text(localizations.cancel),
),
ElevatedButton(
onPressed: () {
onPressed: () async {
final value = double.tryParse(_limitController.text);
if (value == null || value <= 0) return;
if (value > 200000) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text(
"Limit To be Set must be less than 200000"),
content:
Text(localizations.limitToBeSetMustBeLessThan200000),
behavior: SnackBarBehavior.floating,
backgroundColor: theme.colorScheme.error,
),
);
} else {
service.editLimit(value);
Navigator.of(dialogContext).pop(value);
try {
await service.getOtpTLimit(
mobileNumber: widget.mobileNumber);
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ChangeLimitOTPScreen(
newLimit: value.toString(),
mobileNumber: widget.mobileNumber,
),
),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Error: $e"),
behavior: SnackBarBehavior.floating,
backgroundColor: theme.colorScheme.error,
),
);
}
}
},
child: Text(localizations.save),
@@ -164,7 +184,7 @@ class _DailyLimitScreenState extends State<DailyLimitScreen> {
if (_currentLimit != null) ...[
const SizedBox(height: 24),
Text(
"Remaining Limit Today", // This should be localized
localizations.remainingLimitToday, // This should be localized
style: theme.textTheme.titleMedium,
),
const SizedBox(height: 4),
@@ -204,14 +224,6 @@ class _DailyLimitScreenState extends State<DailyLimitScreen> {
),
),
const SizedBox(height: 16),
// TextButton.icon(
// onPressed: _removeLimit,
// icon: const Icon(Icons.remove_circle_outline),
// label: Text(localizations.removeLimit),
// style: TextButton.styleFrom(
// foregroundColor: theme.colorScheme.error,
// ),
// ),
],
),
],

View File

@@ -23,42 +23,50 @@ class PreferenceScreen extends StatelessWidget {
return Stack(
children: [
ListView(
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
children: [
//Set Prefered Username
// ListTile(
// leading: const Icon(Icons.person),
// title: const Text("Set Prefered Username"),
// onTap: () {
// }),
// Language Selection
ListTile(
leading: const Icon(Icons.language),
title: Text(loc.language),
onTap: () {
showDialog(
context: context,
builder: (_) => const LanguageDialog(),
);
},
),
//Theme Mode Switch (Light/Dark)
ListTile(
leading: const Icon(Icons.brightness_6),
title: Text(AppLocalizations.of(context).themeMode),
onTap: () {
showThemeModeDialog(context);
},
),
//Color_Theme_Selection
ListTile(
leading: const Icon(Icons.color_lens),
title: Text(AppLocalizations.of(context).themeColor),
Card(
margin: const EdgeInsets.only(bottom: 10),
child: ListTile(
leading: const Icon(Icons.language),
title: Text(loc.language),
trailing: const Icon(Icons.chevron_right),
onTap: () {
showDialog(
context: context,
builder: (_) => const ColorThemeDialog(),
builder: (_) => const LanguageDialog(),
);
}),
},
),
),
//Theme Mode Switch (Light/Dark)
Card(
margin: const EdgeInsets.only(bottom: 10),
child: ListTile(
leading: const Icon(Icons.brightness_6),
title: Text(AppLocalizations.of(context).themeMode),
trailing: const Icon(Icons.chevron_right),
onTap: () {
showThemeModeDialog(context);
},
),
),
//Color_Theme_Selection
Card(
margin: const EdgeInsets.only(bottom: 10),
child: ListTile(
leading: const Icon(Icons.color_lens),
title: Text(AppLocalizations.of(context).themeColor),
trailing: const Icon(Icons.chevron_right),
onTap: () {
showDialog(
context: context,
builder: (_) => const ColorThemeDialog(),
);
}),
),
],
),
IgnorePointer(

View File

@@ -7,8 +7,8 @@ import 'package:kmobile/features/profile/logout_dialog.dart';
import 'package:kmobile/features/profile/security_settings_screen.dart';
import 'package:kmobile/security/secure_storage.dart';
import 'package:local_auth/local_auth.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import '../../di/injection.dart';
import '../../l10n/app_localizations.dart';
import 'package:kmobile/features/profile/preferences/preference_screen.dart';
@@ -36,8 +36,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
}
Future<String> _getAppVersion() async {
final PackageInfo info = await PackageInfo.fromPlatform();
return 'Version ${info.version} (${info.buildNumber})';
return 'Version 1.0.1 (1))';
}
Future<void> _loadBiometricStatus() async {
@@ -188,12 +187,11 @@ class _ProfileScreenState extends State<ProfileScreen> {
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: theme.colorScheme.surface,
image: const DecorationImage(
image: AssetImage('assets/images/logo.png'),
fit: BoxFit.cover,
child: const CircleAvatar(
radius: 50,
child: Icon(
Symbols.person,
size: 56,
),
),
),
@@ -221,17 +219,6 @@ class _ProfileScreenState extends State<ProfileScreen> {
],
),
),
// Edit/Profile button (optional)
TextButton.icon(
onPressed: () {
// TODO: Navigate to edit profile if required
},
icon: const Icon(Icons.edit, size: 18),
label: const Text("Edit"),
style: TextButton.styleFrom(
foregroundColor: theme.colorScheme.onSurface,
),
),
],
),
),
@@ -285,7 +272,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DailyLimitScreen(),
builder: (context) =>
DailyLimitScreen(mobileNumber: widget.mobileNumber),
),
);
},
@@ -417,25 +405,6 @@ class _ProfileScreenState extends State<ProfileScreen> {
const SizedBox(height: 24),
],
),
// ===== Watermark (kept subtle, no theme change) =====
IgnorePointer(
child: Positioned.fill(
child: Center(
child: Opacity(
opacity: 0.06,
child: ClipOval(
child: Image.asset(
'assets/images/logo.png',
width: 200,
height: 200,
filterQuality: FilterQuality.medium,
),
),
),
),
),
),
],
),
);

View File

@@ -24,99 +24,107 @@ class SecuritySettingsScreen extends StatelessWidget {
body: Stack(
children: [
ListView(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
children: [
ListTile(
leading: const Icon(Icons.lock_outline),
title: Text(loc.changeLoginPassword),
trailing: const Icon(Icons.chevron_right),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChangePasswordScreen(
mobileNumber: mobileNumber,
),
),
);
},
),
Divider(height: 1, color: Theme.of(context).dividerColor),
ListTile(
leading: const Icon(Icons.pin),
title: Text(loc.changeMpin),
trailing: const Icon(Icons.chevron_right),
onTap: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ChangeMpinScreen(),
),
);
if (result == true && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(loc.mpinChangedSuccessfully),
backgroundColor:
Theme.of(context).colorScheme.secondary,
Card(
margin: const EdgeInsets.only(bottom: 10),
child: ListTile(
leading: const Icon(Icons.lock_outline),
title: Text(loc.changeLoginPassword),
trailing: const Icon(Icons.chevron_right),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChangePasswordScreen(
mobileNumber: mobileNumber,
),
),
);
}
},
},
),
),
Divider(height: 1, color: Theme.of(context).dividerColor),
ListTile(
leading: const Icon(Icons.password),
title: const Text('Change TPIN'),
trailing: const Icon(Icons.chevron_right),
onTap: () async {
final authService = getIt<AuthService>();
final isTpinSet = await authService.checkTpin();
Card(
margin: const EdgeInsets.only(bottom: 10),
child: ListTile(
leading: const Icon(Icons.pin),
title: Text(loc.changeMpin),
trailing: const Icon(Icons.chevron_right),
onTap: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ChangeMpinScreen(),
),
);
if (!isTpinSet) {
if (context.mounted) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('TPIN Not Set'),
content: const Text(
'You have not set a TPIN yet. Please set a TPIN to proceed.'),
actions: <Widget>[
TextButton(
child: const Text('Back'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('Proceed'),
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
const TpinSetScreen(),
),
);
},
),
],
);
},
);
}
} else {
if (context.mounted) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
ChangeTpinScreen(mobileNumber: mobileNumber),
if (result == true && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(loc.mpinChangedSuccessfully),
backgroundColor:
Theme.of(context).colorScheme.secondary,
),
);
}
}
},
},
),
),
Card(
margin: const EdgeInsets.only(bottom: 10),
child: ListTile(
leading: const Icon(Icons.password),
title: const Text('Change TPIN'),
trailing: const Icon(Icons.chevron_right),
onTap: () async {
final authService = getIt<AuthService>();
final isTpinSet = await authService.checkTpin();
if (!isTpinSet) {
if (context.mounted) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('TPIN Not Set'),
content: const Text(
'You have not set a TPIN yet. Please set a TPIN to proceed.'),
actions: <Widget>[
TextButton(
child: const Text('Back'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('Proceed'),
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
const TpinSetScreen(),
),
);
},
),
],
);
},
);
}
} else {
if (context.mounted) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
ChangeTpinScreen(mobileNumber: mobileNumber),
),
);
}
}
},
),
),
],
),

View File

@@ -32,6 +32,7 @@ class _QuickPayScreen extends State<QuickPayScreen> {
child: QuickPayManagementTile(
icon: Symbols.input_circle,
label: AppLocalizations.of(context).ownBank,
subtitle: AppLocalizations.of(context).quickownsubtitle,
onTap: () {
Navigator.push(
context,
@@ -49,6 +50,7 @@ class _QuickPayScreen extends State<QuickPayScreen> {
child: QuickPayManagementTile(
icon: Symbols.output_circle,
label: AppLocalizations.of(context).outsideBank,
subtitle: AppLocalizations.of(context).quickoutsidesubtitle,
onTap: () {
Navigator.push(
context,
@@ -87,6 +89,7 @@ class _QuickPayScreen extends State<QuickPayScreen> {
class QuickPayManagementTile extends StatelessWidget {
final IconData icon;
final String label;
final String? subtitle;
final VoidCallback onTap;
final bool disable;
@@ -94,6 +97,7 @@ class QuickPayManagementTile extends StatelessWidget {
super.key,
required this.icon,
required this.label,
this.subtitle,
required this.onTap,
this.disable = false,
});
@@ -133,6 +137,19 @@ class QuickPayManagementTile extends StatelessWidget {
: theme.colorScheme.onSurface,
),
),
if (subtitle != null) // Conditionally display subtitle
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

@@ -143,9 +143,6 @@ class _QuickPayWithinBankScreen extends State<QuickPayWithinBankScreen> {
appBar: AppBar(
title: Text(
AppLocalizations.of(context).quickPayOwnBank,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.w500),
),
centerTitle: false,
),

View File

@@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/branch_service.dart'; // Added: Import BranchService for Atm model and API calls
import 'package:kmobile/di/injection.dart'; // Added: Import for dependency injection (getIt)
import 'package:shimmer/shimmer.dart'; // Added: Import for shimmer loading effect
import 'package:shimmer/shimmer.dart';
import '../../../l10n/app_localizations.dart'; // Added: Import for shimmer loading effect
// Removed: The local 'Location' class is no longer needed as we use the 'Atm' model from branch_service.dart
@@ -60,7 +62,8 @@ class _ATMLocatorScreenState extends State<ATMLocatorScreen> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("ATM Locator"), // Title for the app bar
title: Text(
AppLocalizations.of(context).atmlocator), // Title for the app bar
),
body: Stack(
children: [
@@ -73,7 +76,8 @@ class _ATMLocatorScreenState extends State<ATMLocatorScreen> {
onChanged:
_filterAtms, // Updated: Call _filterAtms on text change
decoration: InputDecoration(
hintText: "Name/Address", // Hint text for the search bar
hintText: AppLocalizations.of(context)
.nameAddress, // Hint text for the search bar
prefixIcon: const Icon(Icons.search), // Search icon
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
@@ -87,9 +91,9 @@ class _ATMLocatorScreenState extends State<ATMLocatorScreen> {
child: _isLoading
? _buildShimmerList() // Display shimmer while loading
: _filteredAtms.isEmpty
? const Center(
child: Text(
"No matching ATMs found")) // Message if no ATMs found
? Center(
child: Text(AppLocalizations.of(context)
.noMatchingAtmsFound)) // Message if no ATMs found
: ListView.builder(
itemCount: _filteredAtms
.length, // Number of items in the filtered list
@@ -128,7 +132,7 @@ class _ATMLocatorScreenState extends State<ATMLocatorScreen> {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: ListTile(
leading: const Icon(Icons.currency_rupee), // Icon for ATM
leading: const Icon(Icons.credit_card), // Icon for ATM
title: Text(atm.name, // Display the ATM's name
style: const TextStyle(fontWeight: FontWeight.bold)),
onTap: () {

View File

@@ -6,6 +6,8 @@ import 'package:kmobile/di/injection.dart';
import 'package:shimmer/shimmer.dart';
import 'package:kmobile/features/service/screens/branch_details_screen.dart';
import '../../../l10n/app_localizations.dart';
class BranchLocatorScreen extends StatefulWidget {
const BranchLocatorScreen({super.key});
@@ -54,10 +56,7 @@ class _BranchLocatorScreenState extends State<BranchLocatorScreen> {
return Scaffold(
appBar: AppBar(
title: Text(
"Branch Locator",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
),
AppLocalizations.of(context).branchlocator,
),
),
body: Stack(
@@ -70,7 +69,7 @@ class _BranchLocatorScreenState extends State<BranchLocatorScreen> {
controller: _searchController,
onChanged: _filterBranches, // Updated
decoration: InputDecoration(
hintText: "Branch Name",
hintText: AppLocalizations.of(context).searchbranch,
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
@@ -84,9 +83,9 @@ class _BranchLocatorScreenState extends State<BranchLocatorScreen> {
child: _isLoading
? _buildShimmerList() // Changed to shimmer
: _filteredBranches.isEmpty
? const Center(
child: Text(
"No matching branches found")) // Updated tex
? Center(
child: Text(AppLocalizations.of(context)
.noMatchingBranchesFound)) // Updated tex
: ListView.builder(
itemCount: _filteredBranches.length,
itemBuilder: (context, index) {
@@ -137,7 +136,7 @@ class _BranchLocatorScreenState extends State<BranchLocatorScreen> {
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: ListTile(
leading: const CircleAvatar(
child: Icon(Icons.location_city),
child: Icon(Icons.account_balance),
),
title: Text(branch.branch_name,
style: const TextStyle(fontWeight: FontWeight.bold)),

View File

@@ -61,7 +61,7 @@ class _ServiceScreen extends State<ServiceScreen> {
Expanded(
child: ServiceManagementTile(
icon: Symbols.location_pin,
label: "ATM Locator",
label: AppLocalizations.of(context).atmlocator,
onTap: () {
Navigator.push(
context,

View File

@@ -225,7 +225,7 @@
"pinMismatch": "PINs do not match",
"securitySettings": "Security Settings",
"kconnect": "Kconnect",
"kccBankFull": "Kangra Central Co-operative Bank",
"kccBankFull": "The Kangra Central Co-operative Bank Ltd.",
"themeColor": "Theme Color",
"selectThemeColor": "Select Theme Color",
"violet": "Violet",
@@ -323,7 +323,7 @@
"details": "Details",
"remarks": "Remarks (Optional)",
"kccbMobile": "KCCB Mobile",
"faq": "Frequently Asked Questions(FAQs)",
"faq": "Frequently Asked Questions",
"branches": "Branches",
"atms": "ATMs",
"dailylimit": "Daily Transaction Limit",
@@ -406,5 +406,154 @@
"rbiCode2": "RBI Code 2",
"latitude": "Latitude",
"address": "Customer Address",
"transactions": "Transactions"
"transactions": "Transactions",
"quickownsubtitle": "Seamlessly send money to your loved ones within Kangra Bank. Fast, secure, and always at your fingertips",
"quickoutsidesubtitle": "Transfer funds to any bank across India with ease. Your transactions are secure and processed quickly",
"ftselfpaysubtitle": "Move money between your own accounts with ease. Your funds, your way",
"ftownsubtitle": "Send money to your saved beneficiaries within Kangra Bank",
"ftoutsidesubtitle": "Transfer funds to your saved beneficiaries in any other bank across India",
"personaldetails": "Personal Details",
"kycdetails": "KYC Details",
"viewall": "View All",
"branchlocator": "Branch Locator",
"atmlocator": "ATM Locator",
"limitSetError": "Limit to be set must be less than {maxAmount}",
"genericError": "Error: {errorMessage}",
"limitUpdatedSuccess": "Limit Updated",
"remainingLimitToday": "Remaining Limit Today",
"branchNameHint": "Branch Name",
"noMatchingBranches": "No matching branches found",
"selfPay": "Self Pay",
"savingsAccountType": "Savings",
"amountExceedsDailyLimit": "Amount exceeds remaining daily limit of ",
"incorrectTpinError": "Please Enter the correct TPIN",
"insufficientFundsError": "Your account does not have sufficient balance",
"somethingWentWrongError": "Something Went Wrong",
"currencyINR": "INR",
"remainingDailyLimit": "Remaining Daily Limit:",
"searchByNameOrAccount": "Search by name or account number",
"beneficiaryCooldownMessage": "Beneficiary will be enabled after the cooldown period.",
"notApplicable": "N/A",
"savingsAccountLabel": "Savings Account",
"loanAccountLabel": "Loan Account",
"termDepositLabel": "Term Deposit",
"recurringDepositLabel": "Recurring Deposit",
"currentAccountLabel": "Current Account",
"unknownAccountLabel": "Unknown Account",
"selectAccountTitle": "Select Account",
"noOtherAccounts": "No other accounts found",
"kccbBankName": "Kangra Central Co-operative Bank",
"fundTransferTitle": "Fund Transfer",
"debitFromLabel": "Debit From",
"creditToLabel": "Credited To",
"remarksOptionalHint": "Remarks (Optional)",
"amountLabel": "Amount",
"amountRequiredError": "Amount is required",
"validAmountError": "Please enter a valid amount",
"fetchingDailyLimitLoader": "Fetching daily limit...",
"proceedButton": "Proceed",
"enterKey": "Enter",
"backKey": "back",
"doneKey": "done",
"transactionDate": "On {date}",
"paymentResultPng": "/payment_result.png",
"rubikFont": "Rubik",
"transactionDateLabel": "Date: {date}",
"utrLabel": "UTR: {utr}",
"searchByNameOrAccountHint": "Search by name or account number",
"savingsAccountDropdown": "Savings",
"beneficiaryExistsError": "Beneficiary already exists",
"somethingWentWrongShort": "Something went Wrong",
"currentAccountDropdown": "Current",
"failedToDeleteBeneficiaryError": "Failed to delete beneficiary: {error}",
"notAvailable": "N/A",
"bankNameLabel": "Bank Name",
"accountNumberLabel": "Account Number",
"accountTypeLabel": "Account Type",
"ifscCodeLabel": "IFSC Code",
"branchNameLabel": "Branch Name",
"enquiryEmailSubject": "Enquiry",
"couldNotOpenEmailApp": "Could not open email app for {email}",
"couldNotOpenDialer": "Could not open dialer for {phone}",
"couldNotLaunchUrl": "Could not launch {url}",
"complaintFormUrl": "https://kccbhp.bank.in/complaint-form/",
"complaintFormTitle": "Complaint Form",
"chairmanEmail": "chairman@kccb.in",
"chairmanPhone": "01892-222677",
"mdEmail": "md@kccb.in",
"mdPhone": "01892-224969",
"gmwEmail": "gmw@kccb.in",
"gmwPhone": "01892-223280",
"gmnEmail": "gmn@kccb.in",
"gmnPhone": "01892-224607",
"atmNameAddressHint": "Name/Address",
"noMatchingAtms": "No matching ATMs found",
"faq1Question": "How do I log in to the mobile banking app?",
"faq1Answer": "You can log in using your customer number and password. Biometric login (fingerprint) and MPIN is also available for supported evices.",
"faq2Question": "Is my banking information secure on this app?",
"faq2Answer": "Yes. We use industry-standard encryption and multi-factor authentication to ensure your data is safe.",
"faq3Question": "How can I check my account balance?",
"faq3Answer": "Once logged in, your account balance will be displayed on the home screen. You can also view detailed balances under the “Accounts” section.",
"faq4Question": "Can I transfer money to other bank accounts?",
"faq4Answer": "Yes. You can use NEFT, RTGS or IMPS to transfer funds to any bank account in India.",
"faq5Question": "How do I view my transaction history?",
"faq5Answer": "Click on the “Account Statement” icon under the Home Screen to view recent and past transactions.",
"chequeEnquiryTitle": "Cheque Enquiry",
"chequeEnquirySubtitle": "You can view the status of your issued cheque book, presented cheques and details of stopped cheques including relevant dates, cheque numbers and other essential information",
"stopChequeSubtitle": "Initiate stop for one or more cheques from your issued checkbook. This essential service helps prevent unauthorized transactions and protects against fraud.",
"chequeEnquiryFailedError": "Failed to fetch cheque status: {error}",
"accountNumberTitle": "Account Number",
"noAccountsFound": "No accounts found",
"searchByChequeDetailsHint": "Search by Cheque Details",
"noChequeStatusFound": "No cheque status found.",
"chequebookIssuedLabel": "Chequebook Issued (CI)",
"branchCodeLabel": "Branch Code:",
"fromChequeLabel": "From Cheque:",
"toChequeLabel": "To Cheque:",
"dateLabel": "Date:",
"chequesCountLabel": "Cheques Count:",
"presentedChequeLabel": "Presented Cheque (PR)",
"chequeNumberLabel": "Cheque Number:",
"transactionCodeLabel": "Transaction Code:",
"amountLabelWithColon": "Amount:",
"statusLabel": "Status:",
"stopChequeLabel": "Stop Cheque (ST)",
"stopIssueDateLabel": "Stop Issue Date:",
"stopExpiryDateLabel": "Stop Expiry Date:",
"emptyString": "",
"cashCreditAccountLabel": "Cash Credit Account",
"stopChequeTitle": "Stop Cheque",
"noChequebookToStop": "No cheque book found to stop cheques from.",
"stopSingleChequeButton": "Stop Single Cheque",
"pleaseSelectAccountFirst": "Please select an account first.",
"stopMultipleChequesButton": "Stop Multiple Cheques",
"noChequeIssuedStatus": "No Cheque Issued status found.",
"chequebookDetailsTitle": "Chequebook Details",
"customerNameLabel": "Customer Name:",
"cifNumberLabel": "CIF Number:",
"startingChequeNumberLabel": "Starting Cheque Number:",
"endingChequeNumberLabel": "Ending Cheque Number:",
"issueDateLabel": "Issue Date:",
"numberOfChequesLabel": "Number of Cheques:",
"instrumentTypeLabel": "Instrument Type:",
"closeButton": "Close",
"stopSingleChequeTitle": "Stop Single Cheque",
"chequeNumberHint": "Cheque Number *",
"pleaseEnterChequeNumberError": "Please enter a cheque number",
"invalidChequeNumberFormatError": "Invalid cheque number format",
"chequeNumberRangeError": "Cheque number must be between {from} and {to}",
"instrumentTypeHint": "Instrument Type *",
"stopIssueDateHint": "Stop Issue Date",
"stopExpiryDateHint": "Stop Expiry Date",
"stopAmountHint": "Stop Amount",
"stopCommentHint": "Stop Comment",
"chequebookIssueDateHint": "Chequebook Issue Date",
"successStatus": "Success",
"errorStatus": "Error",
"incorrectTpinErrorMessage": "The TPIN you entered is incorrect. Please try again.",
"internalServerError": "Internal Server Error",
"stopChequeButton": "Stop Cheque",
"stopMultipleChequesTitle": "Stop Multiple Cheques",
"fromChequeNumberHint": "From Cheque Number *",
"toChequeNumberHint": "To Cheque Number *"
}

View File

@@ -407,5 +407,154 @@
"rbiCode2": "आरबीआई कोड 2",
"latitude": "अक्षांश",
"address": "ग्राहक का पता",
"transactions": "लेनदेन"
"transactions": "लेनदेन",
"quickownsubtitle": "कांगड़ा बैंक के ज़रिए अपने प्रियजनों को आसानी से पैसे भेजें। तेज़, सुरक्षित और हमेशा आपकी उंगलियों पर",
"quickoutsidesubtitle": "भारत भर में किसी भी बैंक में आसानी से धनराशि स्थानांतरित करें। आपके लेन-देन सुरक्षित और शीघ्रता से संसाधित होते हैं",
"ftselfpaysubtitle": "अपने खातों के बीच आसानी से पैसे ट्रांसफर करें। आपका पैसा, आपकी सुविधानुसार",
"ftownsubtitle": "कांगड़ा बैंक के माध्यम से अपने बचत लाभार्थियों को पैसे भेजें",
"ftoutsidesubtitle": "भारत भर में किसी भी अन्य बैंक में अपने सहेजे गए लाभार्थियों को धनराशि हस्तांतरित करें",
"personaldetails": "व्यक्तिगत विवरण",
"kycdetails": "केवाईसी विवरण",
"viewall": "सभी देखें",
"branchlocator": "शाखा लोकेटर",
"atmlocator": "एटीएम लोकेटर",
"limitSetError": "निर्धारित की जाने वाली सीमा {maxAmount} से कम होनी चाहिए।",
"genericError": "त्रुटि: {errorMessage}",
"limitUpdatedSuccess": "सीमा अपडेट हो गई",
"remainingLimitToday": "आज की शेष सीमा",
"branchNameHint": "शाखा का नाम",
"noMatchingBranches": "कोई मेल खाने वाली शाखा नहीं मिली",
"selfPay": "स्वयं भुगतान",
"savingsAccountType": "बचत खाता",
"amountExceedsDailyLimit": "राशि की शेष दैनिक सीमा से अधिक है",
"incorrectTpinError": "कृपया सही टीपिन दर्ज करें",
"insufficientFundsError": "आपके खाते में पर्याप्त शेष राशि नहीं है",
"somethingWentWrongError": "कुछ गलत हो गया",
"currencyINR": "INR",
"remainingDailyLimit": "शेष दैनिक सीमा:",
"searchByNameOrAccount": "नाम या खाता संख्या से खोजें",
"beneficiaryCooldownMessage": "कूलडाउन अवधि के बाद लाभार्थी सक्षम हो जाएगा।",
"notApplicable": "लागू नहीं",
"savingsAccountLabel": "बचत खाता",
"loanAccountLabel": "ऋण खाता",
"termDepositLabel": "सावधि जमा",
"recurringDepositLabel": "आवर्ती जमा",
"currentAccountLabel": "चालू खाता",
"unknownAccountLabel": "अज्ञात खाता",
"selectAccountTitle": "खाता चुनें",
"noOtherAccounts": "कोई अन्य खाता नहीं मिला",
"kccbBankName": "कांगड़ा केंद्रीय सहकारी बैंक",
"fundTransferTitle": "धन हस्तांतरण",
"debitFromLabel": "से डेबिट करें",
"creditToLabel": "को क्रेडिट करें",
"remarksOptionalHint": "टिप्पणी (वैकल्पिक)",
"amountLabel": "राशि",
"amountRequiredError": "राशि आवश्यक है",
"validAmountError": "कृपया एक वैध राशि दर्ज करें",
"fetchingDailyLimitLoader": "दैनिक सीमा लाई जा रही है...",
"proceedButton": "आगे बढ़ें",
"enterKey": "दर्ज करें",
"backKey": "वापस",
"doneKey": "पूर्ण",
"transactionDate": "{date} को",
"paymentResultPng": "/payment_result.png",
"rubikFont": "Rubik",
"transactionDateLabel": "दिनांक: {date}",
"utrLabel": "UTR: {utr}",
"searchByNameOrAccountHint": "नाम या खाता संख्या से खोजें",
"savingsAccountDropdown": "बचत",
"beneficiaryExistsError": "लाभार्थी पहले से मौजूद है",
"somethingWentWrongShort": "कुछ गलत हो गया",
"currentAccountDropdown": "चालू",
"failedToDeleteBeneficiaryError": "लाभार्थी को हटाने में विफल: {error}",
"notAvailable": "उपलब्ध नहीं है",
"bankNameLabel": "बैंक का नाम",
"accountNumberLabel": "खाता संख्या",
"accountTypeLabel": "खाते का प्रकार",
"ifscCodeLabel": "IFSC कोड",
"branchNameLabel": "शाखा का नाम",
"enquiryEmailSubject": "पूछताछ",
"couldNotOpenEmailApp": "{email} के लिए ईमेल ऐप नहीं खोला जा सका",
"couldNotOpenDialer": "{phone} के लिए डायलर नहीं खोला जा सका",
"couldNotLaunchUrl": "{url} लॉन्च नहीं किया जा सका",
"complaintFormUrl": "https://kccbhp.bank.in/complaint-form/",
"complaintFormTitle": "शिकायत प्रपत्र",
"chairmanEmail": "chairman@kccb.in",
"chairmanPhone": "01892-222677",
"mdEmail": "md@kccb.in",
"mdPhone": "01892-224969",
"gmwEmail": "gmw@kccb.in",
"gmwPhone": "01892-223280",
"gmnEmail": "gmn@kccb.in",
"gmnPhone": "01892-224607",
"atmNameAddressHint": "नाम/पता",
"noMatchingAtms": "कोई मेल खाने वाला एटीएम नहीं मिला",
"faq1Question": "मैं मोबाइल बैंकिंग ऐप में कैसे लॉग इन करूं?",
"faq1Answer": "आप अपने ग्राहक नंबर और पासवर्ड का उपयोग करके लॉग इन कर सकते हैं। समर्थित उपकरणों के लिए बायोमेट्रिक लॉगिन (फिंगरप्रिंट) और एमपिन भी उ।",
"faq2Question": "क्या इस ऐप पर मेरी बैंकिंग जानकारी सुरक्षित है?",
"faq2Answer": "हां। हम आपके डेटा को सुरक्षित रखने के लिए उद्योग-मानक एन्क्रिप्शन और बहु-कारक प्रमाणीकरण का उपयोग करते हैं।",
"faq3Question": "मैं अपने खाते की शेष राशि कैसे देख सकता हूं?",
"faq3Answer": "लॉग इन करने के बाद, आपके खाते की शेष राशि होम स्क्रीन पर प्रदर्शित होगी। आप “खाते” अनुभाग के तहत विस्तृत शेष राशि भी देख सकते हैं।",
"faq4Question": "क्या मैं अन्य बैंक खातों में पैसे ट्रांसफर कर सकता हूं?",
"faq4Answer": "हां। आप भारत में किसी भी बैंक खाते में धनराशि स्थानांतरित करने के लिए एनईएफटी, आरटीजीएस या आईएमपीएस का उपयोग कर सकते हैं।",
"faq5Question": "मैं अपना लेनदेन इतिहास कैसे देखूं?",
"faq5Answer": "हाल के और पिछले लेनदेन देखने के लिए होम स्क्रीन के नीचे “खाता विवरण” आइकन पर क्लिक करें।",
"chequeEnquiryTitle": "चेक पूछताछ",
"chequeEnquirySubtitle": "आप अपनी जारी की गई चेक बुक, प्रस्तुत किए गए चेकों और रोके गए चेकों के विवरण देख सकते हैं, जिसमें प्रासंगिक तिथियां, चेक नं्य आवश्यक जानकारी शामिल है",
"stopChequeSubtitle": "अपनी जारी की गई चेकबुक से एक या अधिक चेकों के लिए स्टॉप आरंभ करें। यह आवश्यक सेवा अनधिकृत लेनदेन को रोकने और धोखाधड़ी से बचानद करती है।",
"chequeEnquiryFailedError": "चेक स्थिति लाने में विफल: {error}",
"accountNumberTitle": "खाता संख्या",
"noAccountsFound": "कोई खाता नहीं मिला",
"searchByChequeDetailsHint": "चेक विवरण द्वारा खोजें",
"noChequeStatusFound": "कोई चेक स्थिति नहीं मिली।",
"chequebookIssuedLabel": "चेकबुक जारी (CI)",
"branchCodeLabel": "शाखा कोड:",
"fromChequeLabel": "चेक से:",
"toChequeLabel": "चेक तक:",
"dateLabel": "दिनांक:",
"chequesCountLabel": "चेकों की संख्या:",
"presentedChequeLabel": "प्रस्तुत चेक (PR)",
"chequeNumberLabel": "चेक नंबर:",
"transactionCodeLabel": "लेनदेन कोड:",
"amountLabelWithColon": "राशि:",
"statusLabel": "स्थिति:",
"stopChequeLabel": "चेक रोकें (ST)",
"stopIssueDateLabel": "रोक जारी करने की तारीख:",
"stopExpiryDateLabel": "रोक समाप्ति तिथि:",
"emptyString": "",
"cashCreditAccountLabel": "नकद क्रेडिट खाता",
"stopChequeTitle": "चेक रोकें",
"noChequebookToStop": "से चेक रोकने के लिए कोई चेक बुक नहीं मिली।",
"stopSingleChequeButton": "एकल चेक रोकें",
"pleaseSelectAccountFirst": "कृपया पहले एक खाता चुनें।",
"stopMultipleChequesButton": "एकाधिक चेक रोकें",
"noChequeIssuedStatus": "कोई चेक जारी स्थिति नहीं मिली।",
"chequebookDetailsTitle": "चेकबुक विवरण",
"customerNameLabel": "ग्राहक का नाम:",
"cifNumberLabel": "CIF नंबर:",
"startingChequeNumberLabel": "प्रारंभिक चेक नंबर:",
"endingChequeNumberLabel": "अंतिम चेक नंबर:",
"issueDateLabel": "जारी करने की तारीख:",
"numberOfChequesLabel": "चेकों की संख्या:",
"instrumentTypeLabel": "उपकरण का प्रकार:",
"closeButton": "बंद करें",
"stopSingleChequeTitle": "एकल चेक रोकें",
"chequeNumberHint": "चेक नंबर *",
"pleaseEnterChequeNumberError": "कृपया एक चेक नंबर दर्ज करें",
"invalidChequeNumberFormatError": "अमान्य चेक नंबर प्रारूप",
"chequeNumberRangeError": "चेक नंबर {from} और {to} के बीच होना चाहिए",
"instrumentTypeHint": "उपकरण का प्रकार *",
"stopIssueDateHint": "रोक जारी करने की तारीख",
"stopExpiryDateHint": "रोक समाप्ति तिथि",
"stopAmountHint": "राशि रोकें",
"stopCommentHint": "टिप्पणी रोकें",
"chequebookIssueDateHint": "चेकबुक जारी करने की तारीख",
"successStatus": "सफलता",
"errorStatus": "त्रुटि",
"incorrectTpinErrorMessage": "आपके द्वारा दर्ज किया गया टीपिन गलत है। कृपया पुन: प्रयास करें।",
"internalServerError": "आंतरिक सर्वर त्रुटि",
"stopChequeButton": "चेक रोकें",
"stopMultipleChequesTitle": "एकाधिक चेक रोकें",
"fromChequeNumberHint": "चेक नंबर से *",
"toChequeNumberHint": "चेक नंबर तक *"
}

View File

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

View File

@@ -6,6 +6,7 @@ import FlutterMacOS
import Foundation
import device_info_plus
import flutter_local_notifications
import flutter_secure_storage_macos
import local_auth_darwin
import package_info_plus
@@ -16,6 +17,7 @@ import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
FLALocalAuthPlugin.register(with: registry.registrar(forPlugin: "FLALocalAuthPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))

View File

@@ -69,10 +69,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.3.0"
checked_yaml:
dependency: transitive
description:
@@ -93,18 +93,18 @@ packages:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted
version: "1.1.2"
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.19.1"
version: "1.18.0"
confetti:
dependency: "direct main"
description:
@@ -137,6 +137,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.8"
dbus:
dependency: transitive
description:
name: dbus
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
url: "https://pub.dev"
source: hosted
version: "0.7.11"
device_info_plus:
dependency: "direct main"
description:
@@ -181,10 +189,10 @@ packages:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
version: "1.3.1"
ffi:
dependency: transitive
description:
@@ -238,6 +246,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.2"
flutter_local_notifications:
dependency: "direct main"
description:
name: flutter_local_notifications
sha256: "19ffb0a8bb7407875555e5e98d7343a633bb73707bae6c6a5f37c90014077875"
url: "https://pub.dev"
source: hosted
version: "19.5.0"
flutter_local_notifications_linux:
dependency: transitive
description:
name: flutter_local_notifications_linux
sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5
url: "https://pub.dev"
source: hosted
version: "6.0.0"
flutter_local_notifications_platform_interface:
dependency: transitive
description:
name: flutter_local_notifications_platform_interface
sha256: "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe"
url: "https://pub.dev"
source: hosted
version: "9.1.0"
flutter_local_notifications_windows:
dependency: transitive
description:
name: flutter_local_notifications_windows
sha256: "8d658f0d367c48bd420e7cf2d26655e2d1130147bca1eea917e576ca76668aaf"
url: "https://pub.dev"
source: hosted
version: "1.0.3"
flutter_localizations:
dependency: "direct main"
description: flutter
@@ -385,10 +425,10 @@ packages:
dependency: "direct main"
description:
name: intl
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.dev"
source: hosted
version: "0.20.2"
version: "0.19.0"
jailbreak_root_detection:
dependency: "direct main"
description:
@@ -417,26 +457,26 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
version: "10.0.5"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
url: "https://pub.dev"
source: hosted
version: "3.0.10"
version: "3.0.5"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
version: "3.0.1"
lints:
dependency: transitive
description:
@@ -497,10 +537,10 @@ packages:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.17"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
@@ -521,10 +561,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
version: "1.16.0"
version: "1.15.0"
mime:
dependency: transitive
description:
@@ -541,6 +581,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
open_filex:
dependency: "direct main"
description:
name: open_filex
sha256: "9976da61b6a72302cf3b1efbce259200cd40232643a467aac7370addf94d6900"
url: "https://pub.dev"
source: hosted
version: "4.7.0"
package_info_plus:
dependency: "direct main"
description:
@@ -561,10 +609,10 @@ packages:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.9.0"
path_parsing:
dependency: transitive
description:
@@ -725,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:
@@ -813,11 +869,19 @@ 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
source: sdk
version: "0.0.0"
version: "0.0.99"
source_span:
dependency: transitive
description:
@@ -838,18 +902,18 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.4"
version: "2.1.2"
string_scanner:
dependency: transitive
description:
@@ -870,10 +934,18 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
url: "https://pub.dev"
source: hosted
version: "0.7.6"
version: "0.7.2"
timezone:
dependency: transitive
description:
name: timezone
sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1
url: "https://pub.dev"
source: hosted
version: "0.10.1"
typed_data:
dependency: transitive
description:
@@ -947,7 +1019,7 @@ packages:
source: hosted
version: "3.1.4"
uuid:
dependency: transitive
dependency: "direct main"
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
@@ -982,10 +1054,10 @@ packages:
dependency: transitive
description:
name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.1.4"
vm_service:
dependency: transitive
description:
@@ -1043,5 +1115,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.8.0-0 <4.0.0"
dart: ">=3.5.0 <4.0.0"
flutter: ">=3.24.0"

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
@@ -61,6 +61,11 @@ dependencies:
device_info_plus: ^11.3.0
showcaseview: ^2.0.3
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"
@@ -114,6 +119,8 @@ flutter:
- assets/images/yes_bank_logo.png
- assets/images/uco_logo.png
- assets/images/ipos_logo.png
- assets/images/profile.svg
- assets/images/profile.png
- assets/animations/rupee.json
- assets/animations/error.json
- assets/animations/done.json

View File

@@ -11,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
flutter_local_notifications_windows
)
set(PLUGIN_BUNDLED_LIBRARIES)