Compare commits
23 Commits
b19bc2e222
...
f024f200a4
| Author | SHA1 | Date | |
|---|---|---|---|
| f024f200a4 | |||
| 54a7b8f1b0 | |||
| 60270a5fa9 | |||
| 9d8c8dc8bd | |||
| 537a4faa62 | |||
| 149d4dbc83 | |||
| 3fa40f133a | |||
| 07d5ea8fbe | |||
| 72a2c56392 | |||
| aef82237ac | |||
| 974f42bf95 | |||
| 4a8c69bb1e | |||
| 86aaaa1f6d | |||
| 6796793aac | |||
| fbf6df7181 | |||
| c7111d518a | |||
| 5d307607fd | |||
| 992092052a | |||
| 64fedabd89 | |||
| 4fc6f54fcd | |||
| 8c7e94759a | |||
| 8aa5b170ca | |||
| 04a1ce26ec |
@@ -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'
|
||||
}
|
||||
|
||||
BIN
android/app/src/main/res/drawable/notification_icon.png
Normal file
BIN
android/app/src/main/res/drawable/notification_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 90 KiB |
BIN
assets/images/profile.png
Normal file
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
BIN
assets/images/profile.svg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -105,7 +105,7 @@ class BranchService {
|
||||
if (response.statusCode == 200) {
|
||||
return Branch.listFromJson(response.data);
|
||||
} else {
|
||||
throw Exception("Failed to fetch beneficiaries");
|
||||
throw Exception("Failed to fetch");
|
||||
}
|
||||
} catch (e) {
|
||||
return [];
|
||||
|
||||
138
lib/api/services/cheque_service.dart
Normal file
138
lib/api/services/cheque_service.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,7 +76,7 @@ Dio _createDioClient() {
|
||||
final dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl:
|
||||
'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com:8080', //test
|
||||
'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com', //test
|
||||
//'http://lb-kccb-mobile-banking-app-848675342.ap-south-1.elb.amazonaws.com', //prod
|
||||
//'https://kccbmbnk.net', //prod small
|
||||
connectTimeout: const Duration(seconds: 60),
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
import 'package:pdf/pdf.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
import 'package:kmobile/data/models/transaction.dart';
|
||||
import 'package:kmobile/data/repositories/transaction_repository.dart';
|
||||
@@ -9,11 +12,9 @@ import 'package:kmobile/di/injection.dart';
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
import 'transaction_details_screen.dart';
|
||||
import 'package:pdf/widgets.dart' as pw;
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:open_filex/open_filex.dart';
|
||||
import 'package:kmobile/data/models/user.dart';
|
||||
|
||||
class AccountStatementScreen extends StatefulWidget {
|
||||
@@ -38,13 +39,50 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
||||
List<Transaction> _transactions = [];
|
||||
final _minAmountController = TextEditingController();
|
||||
final _maxAmountController = TextEditingController();
|
||||
//Future<Map<String, dynamic>?>? accountStatementsFuture;
|
||||
late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
selectedUser = widget.users[widget.selectedIndex];
|
||||
_loadTransactions();
|
||||
_initializeNotifications();
|
||||
}
|
||||
|
||||
void _initializeNotifications() async {
|
||||
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||
|
||||
const AndroidInitializationSettings initializationSettingsAndroid =
|
||||
AndroidInitializationSettings('notification_icon');
|
||||
|
||||
const InitializationSettings initializationSettings =
|
||||
InitializationSettings(android: initializationSettingsAndroid);
|
||||
|
||||
await flutterLocalNotificationsPlugin.initialize(
|
||||
initializationSettings,
|
||||
onDidReceiveNotificationResponse: _onDidReceiveNotificationResponse,
|
||||
);
|
||||
_requestNotificationPermission();
|
||||
}
|
||||
|
||||
void _onDidReceiveNotificationResponse(
|
||||
NotificationResponse notificationResponse) async {
|
||||
final String? payload = notificationResponse.payload;
|
||||
if (payload != null && payload.isNotEmpty) {
|
||||
await OpenFilex.open(payload);
|
||||
}
|
||||
}
|
||||
|
||||
void _requestNotificationPermission() async {
|
||||
if (Platform.isAndroid) {
|
||||
final AndroidFlutterLocalNotificationsPlugin? androidImplementation =
|
||||
flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>();
|
||||
|
||||
if (androidImplementation != null) {
|
||||
await androidImplementation.requestNotificationsPermission();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadTransactions() async {
|
||||
@@ -145,12 +183,25 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Card(
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).accountNumber,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500, fontSize: 14),
|
||||
fontWeight: FontWeight.w500, fontSize: 17),
|
||||
),
|
||||
const VerticalDivider(
|
||||
width: 20,
|
||||
thickness: 1,
|
||||
indent: 5,
|
||||
endIndent: 5,
|
||||
color: Colors.grey),
|
||||
DropdownButton<User>(
|
||||
value: selectedUser,
|
||||
onChanged: (User? newUser) {
|
||||
@@ -167,9 +218,18 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
||||
child: Text(user.accountNo.toString()),
|
||||
);
|
||||
}).toList(),
|
||||
underline: Container(), // Remove the underline
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
Row(
|
||||
Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Card(
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"${AppLocalizations.of(context).availableBalance}: ",
|
||||
@@ -181,7 +241,8 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
||||
style: const TextStyle(fontSize: 17)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context).filters,
|
||||
style: const TextStyle(fontSize: 17),
|
||||
@@ -300,11 +361,14 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
||||
Theme.of(context).colorScheme.onSurface,
|
||||
)),
|
||||
)
|
||||
: ListView.separated(
|
||||
: ListView.builder(
|
||||
itemCount: _transactions.length,
|
||||
itemBuilder: (context, index) {
|
||||
final tx = _transactions[index];
|
||||
return ListTile(
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 0, vertical: 4),
|
||||
child: ListTile(
|
||||
leading: Icon(
|
||||
tx.type == 'CR'
|
||||
? Symbols.call_received
|
||||
@@ -326,8 +390,10 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
trailing: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.end,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"₹${tx.amount}",
|
||||
@@ -351,12 +417,9 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return Divider(
|
||||
color: Theme.of(context).dividerColor);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -388,171 +451,194 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
||||
}
|
||||
|
||||
Future<void> _exportToPdf() async {
|
||||
// Step 1: Check if there are any transactions to export.
|
||||
if (_transactions.isEmpty) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('No transactions to export.'),
|
||||
),
|
||||
const SnackBar(content: Text('No transactions to export.')),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var logo = await rootBundle.load('assets/images/logo.png');
|
||||
var rubik = await rootBundle.load("assets/fonts/Rubik-Regular.ttf");
|
||||
final pdf = pw.Document();
|
||||
await flutterLocalNotificationsPlugin.show(
|
||||
0,
|
||||
'Downloading PDF',
|
||||
'Your account statement is being downloaded...',
|
||||
const NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
'download_channel',
|
||||
'Download Notifications',
|
||||
channelDescription: 'Notifications for PDF downloads',
|
||||
importance: Importance.low,
|
||||
priority: Priority.low,
|
||||
showProgress: true,
|
||||
maxProgress: 0,
|
||||
ongoing: true,
|
||||
icon: 'notification_icon',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
pdf.addPage(pw.MultiPage(
|
||||
margin: const pw.EdgeInsets.all(20),
|
||||
build: (pw.Context context) {
|
||||
return [
|
||||
pw.Row(
|
||||
mainAxisAlignment: pw.MainAxisAlignment.start,
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.center,
|
||||
children: [
|
||||
pw.Image(pw.MemoryImage(logo.buffer.asUint8List()),
|
||||
width: 50, height: 50),
|
||||
pw.SizedBox(width: 20),
|
||||
pw.Text('Account Statement - KCCB',
|
||||
// --- 1. LOAD ASSETS ---
|
||||
final logoImage = pw.MemoryImage(
|
||||
(await rootBundle.load('assets/images/logo.png')).buffer.asUint8List());
|
||||
final timesFont = await rootBundle.load("assets/fonts/Rubik-Regular.ttf");
|
||||
final timesBoldFont = await rootBundle.load("assets/fonts/Rubik-Bold.ttf");
|
||||
final ttf = pw.Font.ttf(timesFont);
|
||||
final ttfBold = pw.Font.ttf(timesBoldFont);
|
||||
|
||||
// --- 2. DEFINE COLORS ---
|
||||
final primaryColor = PdfColor.fromHex("#1a5f3a");
|
||||
final secondaryColor = PdfColor.fromHex("#2e7d32");
|
||||
final debitColor = PdfColor.fromHex("#d32f2f");
|
||||
final lightGreyColor = PdfColor.fromHex("#666");
|
||||
final tableBorderColor = PdfColor.fromHex("#d0d0d0");
|
||||
final lightBgColor = PdfColor.fromHex("#f9f9f9");
|
||||
final warningBgColor = PdfColor.fromHex("#f8d7da");
|
||||
final warningBorderColor = PdfColor.fromHex("#f5c6cb");
|
||||
final warningTextColor = PdfColor.fromHex("#721c24");
|
||||
|
||||
// --- 3. CREATE PDF ---
|
||||
final pdf = pw.Document(
|
||||
theme: pw.ThemeData.withFont(base: ttf, bold: ttfBold),
|
||||
);
|
||||
|
||||
// --- 4. BUILD PAGES ---
|
||||
pdf.addPage(
|
||||
pw.MultiPage(
|
||||
pageFormat: PdfPageFormat.a4.copyWith(
|
||||
marginTop: 15 * PdfPageFormat.mm,
|
||||
marginLeft: 10 * PdfPageFormat.mm,
|
||||
marginRight: 10 * PdfPageFormat.mm,
|
||||
marginBottom: 20 * PdfPageFormat.mm,
|
||||
),
|
||||
header: (context) =>
|
||||
_buildHeader(logoImage, primaryColor, lightGreyColor),
|
||||
footer: (context) {
|
||||
return pw.Center(
|
||||
child: pw.Text(
|
||||
'** This is only for information purpose and not for legal use **',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 24, fontWeight: pw.FontWeight.bold)),
|
||||
]),
|
||||
fontSize: 9,
|
||||
color: lightGreyColor,
|
||||
fontStyle: pw.FontStyle.italic)));
|
||||
},
|
||||
build: (context) => [
|
||||
_buildAccountDetails(
|
||||
customerName: selectedUser.name ?? '',
|
||||
branchCode: selectedUser.branchId ?? '',
|
||||
accountNo: selectedUser.accountNo ?? '',
|
||||
cifNumber: selectedUser.cifNumber ?? '',
|
||||
address: selectedUser.address ?? '',
|
||||
lightGreyColor: lightGreyColor,
|
||||
tableBorderColor: tableBorderColor,
|
||||
lightBgColor: lightBgColor,
|
||||
),
|
||||
_buildWarning(
|
||||
warningBgColor, warningBorderColor, debitColor, warningTextColor),
|
||||
_buildPeriodHeader(
|
||||
primaryColor: primaryColor,
|
||||
fromDate: fromDate,
|
||||
toDate: toDate,
|
||||
),
|
||||
_buildTransactionsTable(
|
||||
transactions: _transactions,
|
||||
primaryColor: primaryColor,
|
||||
secondaryColor: secondaryColor,
|
||||
debitColor: debitColor,
|
||||
tableBorderColor: tableBorderColor,
|
||||
),
|
||||
pw.SizedBox(height: 20),
|
||||
pw.Row(
|
||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
pw.Text('Account Number: ${selectedUser.accountNo}',
|
||||
style:
|
||||
pw.TextStyle(font: pw.Font.ttf(rubik), fontSize: 15)),
|
||||
pw.Text('Account Type: ${selectedUser.productType}',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 15,
|
||||
font: pw.Font.ttf(rubik),
|
||||
)),
|
||||
]),
|
||||
pw.SizedBox(height: 20),
|
||||
pw.Table(border: pw.TableBorder.all(), columnWidths: {
|
||||
0: const pw.FractionColumnWidth(0.2),
|
||||
1: const pw.FractionColumnWidth(0.45),
|
||||
2: const pw.FractionColumnWidth(0.15),
|
||||
3: const pw.FractionColumnWidth(0.20),
|
||||
}, children: [
|
||||
pw.TableRow(
|
||||
children: [
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text('Date')),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text('Description', softWrap: true)),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text('Amount')),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text('Balance')),
|
||||
pw.Text('END OF STATEMENT', style: const pw.TextStyle(fontSize: 12)),
|
||||
],
|
||||
),
|
||||
..._transactions.map<pw.TableRow>((tx) {
|
||||
return pw.TableRow(children: [
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(10),
|
||||
child: pw.Text(tx.date ?? '',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 12,
|
||||
font: pw.Font.ttf(rubik),
|
||||
))),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(10),
|
||||
child: pw.Text(tx.name ?? '',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 12, font: pw.Font.ttf(rubik)))),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(10),
|
||||
child: pw.Text("₹${tx.amount} ${tx.type}",
|
||||
style: pw.TextStyle(
|
||||
fontSize: 12,
|
||||
font: pw.Font.ttf(rubik),
|
||||
))),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(10),
|
||||
child: pw.Text("₹${tx.balance} ${tx.balanceType}",
|
||||
style: pw.TextStyle(
|
||||
fontSize: 12,
|
||||
font: pw.Font.ttf(rubik),
|
||||
))),
|
||||
]);
|
||||
}),
|
||||
])
|
||||
];
|
||||
},
|
||||
footer: (pw.Context context) {
|
||||
return pw.Container(
|
||||
alignment: pw.Alignment.centerRight,
|
||||
margin: const pw.EdgeInsets.only(top: 10),
|
||||
child: pw.Text(
|
||||
'Kangra Central Co-Operative Bank Pvt Ltd. ©. All rights reserved.',
|
||||
style: pw.TextStyle(
|
||||
font: pw.Font.ttf(rubik),
|
||||
fontSize: 8,
|
||||
);
|
||||
|
||||
pdf.addPage(
|
||||
pw.Page(
|
||||
pageFormat: PdfPageFormat.a4.copyWith(
|
||||
marginTop: 15 * PdfPageFormat.mm,
|
||||
marginLeft: 10 * PdfPageFormat.mm,
|
||||
marginRight: 10 * PdfPageFormat.mm,
|
||||
marginBottom: 20 * PdfPageFormat.mm,
|
||||
),
|
||||
build: (context) => _buildLastPage(),
|
||||
),
|
||||
);
|
||||
}));
|
||||
|
||||
//Logic For all platforms
|
||||
// --- 5. SAVE AND NOTIFY ---
|
||||
try {
|
||||
final Uint8List pdfBytes = await pdf.save();
|
||||
final String timestamp = DateTime.now().millisecondsSinceEpoch.toString();
|
||||
final String fileName = 'account_statement_$timestamp.pdf';
|
||||
final String timestamp =
|
||||
DateFormat("ddMMyyyy_HHmm").format(DateTime.now());
|
||||
final String fileName =
|
||||
'Statement_${selectedUser.accountNo}_$timestamp.pdf';
|
||||
|
||||
String? filePath;
|
||||
|
||||
// For Android
|
||||
if (Platform.isAndroid) {
|
||||
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
||||
if (androidInfo.version.sdkInt < 29) {
|
||||
final status = await Permission.storage.status;
|
||||
if (status.isDenied) {
|
||||
final result = await Permission.storage.request();
|
||||
if (result.isDenied) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Storage permission is required to save PDF'),
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final directory = Directory('/storage/emulated/0/Download');
|
||||
if (!await directory.exists()) {
|
||||
await directory.create(recursive: true);
|
||||
}
|
||||
final file = File('${directory.path}/$fileName');
|
||||
await file.writeAsBytes(pdfBytes);
|
||||
filePath = file.path;
|
||||
} else {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final file = await File('${tempDir.path}/$fileName').create();
|
||||
await file.writeAsBytes(pdfBytes);
|
||||
filePath = file.path;
|
||||
}
|
||||
|
||||
await flutterLocalNotificationsPlugin.show(
|
||||
0,
|
||||
'PDF Download Complete',
|
||||
'Your account statement has been saved.',
|
||||
const NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
'download_channel',
|
||||
'Download Notifications',
|
||||
channelDescription: 'Notifications for PDF downloads',
|
||||
importance: Importance.high,
|
||||
priority: Priority.high,
|
||||
showProgress: false,
|
||||
ongoing: false,
|
||||
autoCancel: true,
|
||||
icon: 'notification_icon',
|
||||
actions: [
|
||||
AndroidNotificationAction('open_pdf', 'Open PDF',
|
||||
showsUserInterface: true)
|
||||
],
|
||||
),
|
||||
),
|
||||
payload: filePath,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('PDF saved to: ${file.path}'),
|
||||
content: Text('PDF saved to: $filePath'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Add for IOS
|
||||
else if (Platform.isIOS) {
|
||||
// On iOS, we save to a temporary directory and then open the share sheet.
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final file = await File('${tempDir.path}/$fileName').create();
|
||||
await file.writeAsBytes(pdfBytes);
|
||||
|
||||
// Use share_plus to open the iOS share dialog
|
||||
await Share.shareXFiles(
|
||||
[XFile(file.path)],
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
await flutterLocalNotificationsPlugin.show(
|
||||
0,
|
||||
'PDF Download Failed',
|
||||
'Error saving PDF: $e',
|
||||
const NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
'download_channel', 'Download Notifications',
|
||||
channelDescription: 'Notifications for PDF downloads',
|
||||
importance: Importance.high,
|
||||
priority: Priority.high,
|
||||
showProgress: false,
|
||||
ongoing: false,
|
||||
icon: 'notification_icon'),
|
||||
),
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
@@ -563,6 +649,404 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
pw.Widget _buildHeader(pw.MemoryImage logoImage, PdfColor primaryColor,
|
||||
PdfColor lightGreyColor) {
|
||||
return pw.Container(
|
||||
margin: const pw.EdgeInsets.only(bottom: 15),
|
||||
padding: const pw.EdgeInsets.only(bottom: 12),
|
||||
decoration: pw.BoxDecoration(
|
||||
border: pw.Border(bottom: pw.BorderSide(color: primaryColor, width: 2)),
|
||||
),
|
||||
child: pw.Row(
|
||||
children: [
|
||||
pw.Image(logoImage, height: 55, width: 55),
|
||||
pw.SizedBox(width: 12),
|
||||
pw.Expanded(
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.Text(
|
||||
'THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 18,
|
||||
color: primaryColor,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
letterSpacing: 0.3),
|
||||
),
|
||||
pw.Text(
|
||||
'Head Office: Dharmsala, District Kangra (H.P.), Pin. 176215',
|
||||
style: pw.TextStyle(fontSize: 10, color: lightGreyColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.end,
|
||||
children: [
|
||||
pw.Text(
|
||||
'e-Statement Service',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 11,
|
||||
color: primaryColor,
|
||||
fontWeight: pw.FontWeight.bold),
|
||||
),
|
||||
pw.SizedBox(height: 2),
|
||||
pw.Text(
|
||||
'Generated: ${DateFormat("dd/MM/yyyy HH:mm").format(DateTime.now())}',
|
||||
style: pw.TextStyle(fontSize: 9, color: lightGreyColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pw.Widget _buildAccountDetails({
|
||||
required String customerName,
|
||||
required String branchCode,
|
||||
required String accountNo,
|
||||
required String cifNumber,
|
||||
required String address,
|
||||
required PdfColor lightGreyColor,
|
||||
required PdfColor tableBorderColor,
|
||||
required PdfColor lightBgColor,
|
||||
}) {
|
||||
const cellPadding = pw.EdgeInsets.symmetric(horizontal: 12, vertical: 8);
|
||||
final border = pw.BorderSide(color: tableBorderColor, width: 1);
|
||||
|
||||
return pw.Table(
|
||||
border: pw.TableBorder(
|
||||
top: border,
|
||||
bottom: border,
|
||||
left: border,
|
||||
right: border,
|
||||
horizontalInside: border,
|
||||
verticalInside: border,
|
||||
),
|
||||
columnWidths: {
|
||||
0: const pw.FlexColumnWidth(1),
|
||||
1: const pw.FlexColumnWidth(1),
|
||||
},
|
||||
children: [
|
||||
pw.TableRow(
|
||||
children: [
|
||||
_buildDetailCell(
|
||||
'Customer Name', customerName, cellPadding, lightGreyColor),
|
||||
_buildDetailCell(
|
||||
'CIF Number', cifNumber, cellPadding, lightGreyColor),
|
||||
],
|
||||
),
|
||||
pw.TableRow(
|
||||
children: [
|
||||
_buildDetailCell(
|
||||
'Account Number', accountNo, cellPadding, lightGreyColor),
|
||||
_buildDetailCell(
|
||||
'Branch Code', branchCode, cellPadding, lightGreyColor),
|
||||
],
|
||||
),
|
||||
pw.TableRow(
|
||||
children: [
|
||||
pw.Container(
|
||||
padding: cellPadding,
|
||||
// Using a Column inside a single cell to potentially wrap long address
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.Text(
|
||||
'Customer Address',
|
||||
style: pw.TextStyle(fontSize: 10, color: lightGreyColor),
|
||||
),
|
||||
pw.SizedBox(height: 2),
|
||||
pw.Text(
|
||||
address,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 11, fontWeight: pw.FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Empty container for the second column in this row, as it's a single spanning column
|
||||
pw.Container(),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
pw.Widget _buildDetailCell(String label, String value, pw.EdgeInsets padding,
|
||||
PdfColor lightGreyColor,
|
||||
{bool vertical = false}) {
|
||||
final children = [
|
||||
pw.Text(
|
||||
label,
|
||||
style: pw.TextStyle(fontSize: 10, color: lightGreyColor),
|
||||
),
|
||||
if (vertical) pw.SizedBox(height: 2),
|
||||
pw.Text(
|
||||
value,
|
||||
style: pw.TextStyle(fontSize: 11, fontWeight: pw.FontWeight.bold),
|
||||
),
|
||||
];
|
||||
return pw.Padding(
|
||||
padding: padding,
|
||||
child: vertical
|
||||
? pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
mainAxisAlignment: pw.MainAxisAlignment.start,
|
||||
children: children)
|
||||
: pw.Row(
|
||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||
children: children),
|
||||
);
|
||||
}
|
||||
|
||||
pw.Widget _buildWarning(PdfColor warningBgColor, PdfColor warningBorderColor,
|
||||
PdfColor debitColor, PdfColor warningTextColor) {
|
||||
return pw.Container(
|
||||
margin: const pw.EdgeInsets.symmetric(vertical: 15),
|
||||
padding: const pw.EdgeInsets.all(10),
|
||||
decoration: pw.BoxDecoration(
|
||||
color: warningBgColor,
|
||||
border: pw.Border.all(color: warningBorderColor, width: 1),
|
||||
borderRadius: pw.BorderRadius.circular(4),
|
||||
),
|
||||
child: pw.Row(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.SizedBox(width: 10),
|
||||
pw.Expanded(
|
||||
child: pw.RichText(
|
||||
text: pw.TextSpan(
|
||||
style: pw.TextStyle(
|
||||
fontSize: 10, color: warningTextColor, lineSpacing: 1.5),
|
||||
children: [
|
||||
pw.TextSpan(
|
||||
text: 'NEVER SHARE ',
|
||||
style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
|
||||
const pw.TextSpan(
|
||||
text:
|
||||
'your Card number, CVV, PIN, OTP, Internet Banking User ID, Password or URB with anyone even if the caller claims to be a bank employee. Sharing these details can lead to unauthorized access to your account.'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pw.Widget _buildPeriodHeader(
|
||||
{required PdfColor primaryColor, DateTime? fromDate, DateTime? toDate}) {
|
||||
String from = fromDate != null
|
||||
? DateFormat('dd/MM/yyyy').format(fromDate)
|
||||
: 'the beginning';
|
||||
String to =
|
||||
toDate != null ? DateFormat('dd/MM/yyyy').format(toDate) : 'today';
|
||||
|
||||
return pw.Container(
|
||||
margin: const pw.EdgeInsets.only(bottom: 12),
|
||||
padding: const pw.EdgeInsets.all(10),
|
||||
decoration: pw.BoxDecoration(
|
||||
border: pw.Border.symmetric(
|
||||
horizontal: pw.BorderSide(color: primaryColor, width: 2)),
|
||||
color: PdfColor.fromHex("#f5f5f5"),
|
||||
),
|
||||
alignment: pw.Alignment.center,
|
||||
child: pw.RichText(
|
||||
text: pw.TextSpan(
|
||||
style: pw.TextStyle(
|
||||
fontSize: 13,
|
||||
color: primaryColor,
|
||||
fontWeight: pw.FontWeight.bold),
|
||||
children: [
|
||||
const pw.TextSpan(text: 'Account statement from '),
|
||||
pw.TextSpan(
|
||||
text: from,
|
||||
style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
|
||||
const pw.TextSpan(text: ' to '),
|
||||
pw.TextSpan(
|
||||
text: to, style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pw.Widget _buildTransactionsTable({
|
||||
required List<Transaction> transactions,
|
||||
required PdfColor primaryColor,
|
||||
required PdfColor secondaryColor,
|
||||
required PdfColor debitColor,
|
||||
required PdfColor tableBorderColor,
|
||||
}) {
|
||||
//final border = pw.BorderSide(color: tableBorderColor, width: 1);
|
||||
|
||||
return pw.Table(
|
||||
border: pw.TableBorder.all(color: tableBorderColor, width: 1),
|
||||
columnWidths: {
|
||||
0: const pw.FlexColumnWidth(1.5),
|
||||
1: const pw.FlexColumnWidth(4),
|
||||
2: const pw.FlexColumnWidth(2.2),
|
||||
3: const pw.FlexColumnWidth(2.2),
|
||||
},
|
||||
children: [
|
||||
pw.TableRow(
|
||||
decoration: pw.BoxDecoration(color: secondaryColor),
|
||||
children: [
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(10),
|
||||
child: pw.Text('Date',
|
||||
textAlign: pw.TextAlign.center,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: PdfColors.white)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(10),
|
||||
child: pw.Text('Mode / Particulars',
|
||||
textAlign: pw.TextAlign.left,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: PdfColors.white)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(10),
|
||||
child: pw.Text('Withdrawals / Deposits',
|
||||
textAlign: pw.TextAlign.center,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: PdfColors.white)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(10),
|
||||
child: pw.Text('Balance',
|
||||
textAlign: pw.TextAlign.center,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: PdfColors.white)),
|
||||
),
|
||||
],
|
||||
),
|
||||
...transactions.map((tx) {
|
||||
final amount = double.tryParse(tx.amount ?? '0') ?? 0;
|
||||
final isDebit = tx.type == 'DR';
|
||||
return pw.TableRow(
|
||||
decoration: const pw.BoxDecoration(color: PdfColors.white),
|
||||
children: [
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Text(tx.date ?? '',
|
||||
textAlign: pw.TextAlign.center,
|
||||
style: const pw.TextStyle(fontSize: 11)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Text(tx.name ?? '',
|
||||
textAlign: pw.TextAlign.left,
|
||||
style: const pw.TextStyle(fontSize: 11)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Text(
|
||||
'${NumberFormat.currency(locale: 'en_IN', symbol: '₹').format(amount)} ${tx.type}',
|
||||
textAlign: pw.TextAlign.right,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 11,
|
||||
color: isDebit ? debitColor : secondaryColor,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Text(
|
||||
'${NumberFormat.currency(locale: 'en_IN', symbol: '₹').format(double.tryParse(tx.balance ?? '0') ?? 0)} ${tx.balanceType ?? ''}',
|
||||
textAlign: pw.TextAlign.right,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 11, fontWeight: pw.FontWeight.bold),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
pw.Widget _buildLastPage() {
|
||||
return pw.Container(
|
||||
padding: const pw.EdgeInsets.all(20),
|
||||
decoration: pw.BoxDecoration(
|
||||
border: pw.Border.all(color: PdfColors.black, width: 2),
|
||||
color: PdfColor.fromHex("#fafafa"),
|
||||
borderRadius: pw.BorderRadius.circular(4),
|
||||
),
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.Text(
|
||||
'IMPORTANT INFORMATION:',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: PdfColors.black,
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 15),
|
||||
..._buildInfoPoints(PdfColors.black, [
|
||||
'The Kangra Central Cooperative Bank Officials or representatives will NEVER ask you for your personal information i.e. your card details, passwords, PIN, CVV, OTP etc. Do not share such details with anyone over phone, SMS or email.',
|
||||
'Always stay vigilant to suspicious emails. Do not open any suspicious emails.',
|
||||
'Always stay vigilant when giving out sensitive personal or account information.',
|
||||
'Beware of messages that instill a sense of urgency (e.g., account will expire unless you "verify" your information). Contact the Bank directly if unsure.',
|
||||
'Always log out of secondary devices and reset your passwords frequently.',
|
||||
'Use strong passwords: Create strong passwords that are difficult for hackers to guess.',
|
||||
'Use public Wi-Fi with caution: Be careful when using public Wi-Fi networks.',
|
||||
'Back up your data regularly to a secure, encrypted, off-site location.',
|
||||
'Follow corporate security policies: Adhere to your company\'s security guidelines.',
|
||||
'Assess third-party app permissions carefully before granting access.',
|
||||
'Lock your computer and mobile phone when not in use.',
|
||||
'Don\'t leave devices unattended. Keep all mobile devices, such as laptops and cell phones, physically secured.',
|
||||
'Don\'t leave Bluetooth / Wireless turned on when not in use. Enable them only when needed and in a safe environment.',
|
||||
]),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<pw.Widget> _buildInfoPoints(PdfColor primaryColor, List<String> points) {
|
||||
return points.map((point) {
|
||||
return pw.Padding(
|
||||
padding: const pw.EdgeInsets.only(bottom: 10),
|
||||
child: pw.Row(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.Container(
|
||||
width: 15,
|
||||
child: pw.Text('*',
|
||||
style: pw.TextStyle(
|
||||
color: primaryColor,
|
||||
fontSize: 12,
|
||||
fontWeight: pw.FontWeight.bold))),
|
||||
pw.Expanded(
|
||||
child: pw.Text(
|
||||
point,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 11,
|
||||
lineSpacing: 1.6,
|
||||
fontWeight: pw.FontWeight.bold),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
Widget buildDateBox(String label, DateTime? date) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
|
||||
@@ -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,19 +45,26 @@ 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(
|
||||
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),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0, vertical: 8.0),
|
||||
child: _buildAccountCard(user),
|
||||
);
|
||||
},
|
||||
),
|
||||
), // Closing Expanded
|
||||
], // Closing Column
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
361
lib/features/cheque/screens/cheque_enquiry_screen.dart
Normal file
361
lib/features/cheque/screens/cheque_enquiry_screen.dart
Normal 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 ?? ''),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,77 +1,83 @@
|
||||
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(
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
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,
|
||||
Expanded(
|
||||
child: ChequeManagementCardTile(
|
||||
icon: Symbols.payments,
|
||||
label: AppLocalizations.of(context).chequeEnquiryTitle,
|
||||
subtitle:
|
||||
AppLocalizations.of(context).chequeEnquirySubtitle,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const EnquiryScreen()),
|
||||
builder: (context) => ChequeEnquiryScreen(
|
||||
users: users,
|
||||
selectedIndex: selectedAccountIndex,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||
ChequeManagementTile(
|
||||
icon: Symbols.approval_delegation,
|
||||
label: AppLocalizations.of(context).chequeDeposit,
|
||||
onTap: () {},
|
||||
),
|
||||
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||
ChequeManagementTile(
|
||||
icon: Symbols.front_hand,
|
||||
Expanded(
|
||||
child: ChequeManagementCardTile(
|
||||
icon: Symbols.block_sharp,
|
||||
label: AppLocalizations.of(context).stopCheque,
|
||||
onTap: () {},
|
||||
subtitle: AppLocalizations.of(context).stopChequeSubtitle,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StopChequeScreen(
|
||||
users: users,
|
||||
selectedIndex: selectedAccountIndex,
|
||||
),
|
||||
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||
ChequeManagementTile(
|
||||
icon: Symbols.cancel_presentation,
|
||||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
IgnorePointer(
|
||||
child: Center(
|
||||
child: Opacity(
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
349
lib/features/cheque/screens/stop_cheque_screen.dart
Normal file
349
lib/features/cheque/screens/stop_cheque_screen.dart
Normal 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 ?? ''),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
287
lib/features/cheque/screens/stop_multiple_cheques_screen.dart
Normal file
287
lib/features/cheque/screens/stop_multiple_cheques_screen.dart
Normal 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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
256
lib/features/cheque/screens/stop_single_cheque_screen.dart
Normal file
256
lib/features/cheque/screens/stop_single_cheque_screen.dart
Normal 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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
@@ -33,74 +35,123 @@ class _CustomerInfoScreenState extends State<CustomerInfoScreen> {
|
||||
.replaceFirst(RegExp('\n'), ''),
|
||||
),
|
||||
),
|
||||
body: Stack(
|
||||
body: SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SafeArea(
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 30),
|
||||
CircleAvatar(
|
||||
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: Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 56,
|
||||
height: 56,
|
||||
child: CircleAvatar(
|
||||
radius: 50,
|
||||
child: SvgPicture.asset(
|
||||
'assets/images/avatar_male.svg',
|
||||
width: 150,
|
||||
height: 150,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0),
|
||||
child: Text(
|
||||
user.name ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: theme.colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w500,
|
||||
child: Icon(
|
||||
Symbols.person,
|
||||
size: 56,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// Name + mobile
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${AppLocalizations.of(context).cif}: ${user.cifNumber ?? 'N/A'}',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: theme.colorScheme.onSurfaceVariant),
|
||||
// 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: 30),
|
||||
InfoField(
|
||||
label: AppLocalizations.of(context).activeAccounts,
|
||||
value: user.activeAccounts?.toString() ?? 'N/A',
|
||||
),
|
||||
InfoField(
|
||||
label: AppLocalizations.of(context).mobileNumber,
|
||||
value: user.mobileNo ?? 'N/A',
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
user.cifNumber ?? '',
|
||||
style:
|
||||
theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onSurface
|
||||
.withOpacity(0.7),
|
||||
),
|
||||
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
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
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: 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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -120,6 +171,64 @@ class _CustomerInfoScreenState extends State<CustomerInfoScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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(
|
||||
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: Transform.translate(
|
||||
offset: isSelected ? const Offset(10.0, 0.0) : Offset.zero,
|
||||
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,
|
||||
@@ -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,28 +371,47 @@ 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: 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).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.onPrimary,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
// 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: () {
|
||||
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(
|
||||
@@ -434,27 +422,16 @@ class _DashboardScreenState extends State<DashboardScreen>
|
||||
),
|
||||
);
|
||||
},
|
||||
child: CircleAvatar(
|
||||
backgroundColor: Colors.grey[200],
|
||||
radius: 20,
|
||||
child: SvgPicture.asset(
|
||||
'assets/images/avatar_male.svg',
|
||||
width: 40,
|
||||
height: 40,
|
||||
fit: BoxFit.cover,
|
||||
child: const CircleAvatar(
|
||||
radius: 21,
|
||||
child: Icon(
|
||||
Symbols.person,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
AppLocalizations.of(context).kccbMobile,
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.primary,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
centerTitle: true,
|
||||
],
|
||||
),
|
||||
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';
|
||||
// first‐time 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,28 +478,16 @@ 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
|
||||
SizedBox(
|
||||
height: 160,
|
||||
child: PageView.builder(
|
||||
clipBehavior: Clip.none,
|
||||
controller: _pageController,
|
||||
itemCount: users.length,
|
||||
clipBehavior: Clip
|
||||
.none, // Keep this to show adjacent cards
|
||||
padEnds: false,
|
||||
itemCount:
|
||||
users.length, // Keep this to show adjacent cards
|
||||
|
||||
onPageChanged: (int newIndex) async {
|
||||
if (newIndex == selectedAccountIndex)
|
||||
return;
|
||||
if (newIndex == selectedAccountIndex) return;
|
||||
|
||||
// Hide the balance of the old card when scrolling away
|
||||
final oldAccountNo =
|
||||
@@ -536,23 +502,36 @@ class _DashboardScreenState extends State<DashboardScreen>
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
final user = users[index];
|
||||
final isSelected =
|
||||
index == selectedAccountIndex;
|
||||
return _buildAccountCard(
|
||||
user, isSelected);
|
||||
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,
|
||||
),
|
||||
),
|
||||
// View All tab part, painted on top
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: _buildViewAllTab(users),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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),
|
||||
|
||||
120
lib/features/profile/change_limit_otp_screen.dart
Normal file
120
lib/features/profile/change_limit_otp_screen.dart
Normal 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
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -23,17 +23,16 @@ 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(
|
||||
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,
|
||||
@@ -41,24 +40,33 @@ class PreferenceScreen extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
//Theme Mode Switch (Light/Dark)
|
||||
ListTile(
|
||||
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
|
||||
ListTile(
|
||||
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(
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -24,8 +24,11 @@ class SecuritySettingsScreen extends StatelessWidget {
|
||||
body: Stack(
|
||||
children: [
|
||||
ListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
children: [
|
||||
ListTile(
|
||||
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),
|
||||
@@ -40,8 +43,10 @@ class SecuritySettingsScreen extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||
ListTile(
|
||||
),
|
||||
Card(
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.pin),
|
||||
title: Text(loc.changeMpin),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
@@ -64,8 +69,10 @@ class SecuritySettingsScreen extends StatelessWidget {
|
||||
}
|
||||
},
|
||||
),
|
||||
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||
ListTile(
|
||||
),
|
||||
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),
|
||||
@@ -118,6 +125,7 @@ class SecuritySettingsScreen extends StatelessWidget {
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
IgnorePointer(
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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: () {
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 *"
|
||||
}
|
||||
|
||||
@@ -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": "चेक नंबर तक *"
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
|
||||
56
pubspec.lock
56
pubspec.lock
@@ -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:
|
||||
@@ -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
|
||||
@@ -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:
|
||||
@@ -874,6 +922,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.6"
|
||||
timezone:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timezone
|
||||
sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.1"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -61,6 +61,8 @@ 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
|
||||
# jailbreak_root_detection: "^1.1.6"
|
||||
|
||||
|
||||
@@ -114,6 +116,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
|
||||
|
||||
@@ -11,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
flutter_local_notifications_windows
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
Reference in New Issue
Block a user