13 Commits

Author SHA1 Message Date
b19bc2e222 Extras removed 2025-11-27 11:47:39 +05:30
b9147b30d5 Enquiry_screen ui changed 2025-11-25 14:43:52 +05:30
3358ec7669 UI #1 2025-11-25 12:51:29 +05:30
18db360a45 Code Formatted 2025-11-24 18:18:36 +05:30
b7fe6a9d18 View All Created 2025-11-24 18:16:53 +05:30
adb9a5330b Account Info card UI changed 2025-11-24 15:50:01 +05:30
0075abc906 Bottom navigation changed 2025-11-24 13:26:33 +05:30
353ec63916 3 changes 2025-11-24 12:56:06 +05:30
71b52cfb43 dashboard Screen UI changed 2025-11-24 12:20:35 +05:30
c1df43e9b6 dashboard#1 2025-11-20 17:45:29 +05:30
f0d5233afc Beneficiary lists changed 2025-11-20 13:31:42 +05:30
4fe6af4098 dark Theme and 18 other changes done 2025-11-20 12:33:30 +05:30
fda5d075ff Profile Screen 2025-11-18 11:33:22 +05:30
31 changed files with 1902 additions and 1178 deletions

View File

@@ -1,47 +1,47 @@
import 'package:dio/dio.dart';
class Branch{
final String branch_code;
final String branch_name;
final String zone;
final String tehsil;
final String block;
final String block_code;
final String distt_name;
final String distt_code_slbc;
final String date_of_opening;
final String rbi_code_1;
final String rbi_code_2;
final String telephone_no;
final String type_of_branch;
final String rtgs_acct_no;
final String br_lattitude;
final String br_longitude;
final String pincode;
final String post_office;
class Branch {
final String branch_code;
final String branch_name;
final String zone;
final String tehsil;
final String block;
final String block_code;
final String distt_name;
final String distt_code_slbc;
final String date_of_opening;
final String rbi_code_1;
final String rbi_code_2;
final String telephone_no;
final String type_of_branch;
final String rtgs_acct_no;
final String br_lattitude;
final String br_longitude;
final String pincode;
final String post_office;
Branch({
required this.branch_code,
required this.branch_name,
required this.zone,
required this.tehsil,
required this.block,
required this.block_code,
required this.distt_name,
required this.distt_code_slbc,
required this.date_of_opening,
required this.rbi_code_1,
required this.rbi_code_2,
required this.telephone_no,
required this.type_of_branch,
required this.rtgs_acct_no,
required this.br_lattitude,
required this.br_longitude,
required this.pincode,
required this.post_office,
});
Branch({
required this.branch_code,
required this.branch_name,
required this.zone,
required this.tehsil,
required this.block,
required this.block_code,
required this.distt_name,
required this.distt_code_slbc,
required this.date_of_opening,
required this.rbi_code_1,
required this.rbi_code_2,
required this.telephone_no,
required this.type_of_branch,
required this.rtgs_acct_no,
required this.br_lattitude,
required this.br_longitude,
required this.pincode,
required this.post_office,
});
factory Branch.fromJson(Map<String, dynamic> json) {
factory Branch.fromJson(Map<String, dynamic> json) {
return Branch(
branch_code: json['branch_code'] ?? json['branch_code'] ?? '',
branch_name: json['branch_name'] ?? json['branch_name'] ?? '',
@@ -60,33 +60,32 @@ factory Branch.fromJson(Map<String, dynamic> json) {
br_lattitude: json['br_lattitude'] ?? json['br_lattitude'] ?? '',
br_longitude: json['br_longitude'] ?? json['br_longitude'] ?? '',
pincode: json['pincode'] ?? json['pincode'] ?? '',
post_office: json['post_office'] ?? json['post_office'] ?? '',
post_office: json['post_office'] ?? json['post_office'] ?? '',
);
}
static List<Branch> listFromJson(List<dynamic> jsonList) {
final beneficiaryList = jsonList
.map((beneficiary) => Branch.fromJson(beneficiary))
.toList();
static List<Branch> listFromJson(List<dynamic> jsonList) {
final beneficiaryList =
jsonList.map((beneficiary) => Branch.fromJson(beneficiary)).toList();
return beneficiaryList;
}
}
class Atm {
final String name;
class Atm {
final String name;
Atm({required this.name});
Atm({required this.name});
factory Atm.fromJson(Map<String, dynamic> json) {
return Atm(
name: json['name'] ?? '', // Assuming the API returns a 'name' field
);
}
factory Atm.fromJson(Map<String, dynamic> json) {
return Atm(
name: json['name'] ?? '', // Assuming the API returns a 'name' field
);
}
static List<Atm> listFromJson(List<dynamic> jsonList) {
return jsonList.map((atm) => Atm.fromJson(atm)).toList();
}
}
static List<Atm> listFromJson(List<dynamic> jsonList) {
return jsonList.map((atm) => Atm.fromJson(atm)).toList();
}
}
class BranchService {
final Dio _dio;
@@ -113,7 +112,7 @@ class BranchService {
}
}
Future<List<Atm>> fetchAtmList() async {
Future<List<Atm>> fetchAtmList() async {
try {
final response = await _dio.get(
"/api/atm",
@@ -134,4 +133,4 @@ class BranchService {
return [];
}
}
}
}

View File

@@ -13,7 +13,9 @@ 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/auth/screens/splash_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';
@@ -37,7 +39,6 @@ class KMobile extends StatefulWidget {
class _KMobileState extends State<KMobile> with WidgetsBindingObserver {
Timer? _backgroundTimer;
bool showSplash = true;
Locale? _locale;
@override
@@ -45,11 +46,6 @@ class _KMobileState extends State<KMobile> with WidgetsBindingObserver {
super.initState();
WidgetsBinding.instance.addObserver(this);
loadPreferences();
Future.delayed(const Duration(seconds: 3), () {
setState(() {
showSplash = false;
});
});
}
@override
@@ -131,9 +127,12 @@ class _KMobileState extends State<KMobile> with WidgetsBindingObserver {
theme: themeState.getLightThemeData(),
darkTheme: themeState.getDarkThemeData(),
themeMode: context.watch<ThemeModeCubit>().state.mode,
navigatorObservers: [
getIt<RouteObserver<ModalRoute<void>>>(),
],
onGenerateRoute: AppRoutes.generateRoute,
initialRoute: AppRoutes.splash,
home: showSplash ? const SplashScreen() : const AuthGate(),
home: const AuthGate(),
);
},
);
@@ -205,7 +204,7 @@ class _AuthGateState extends State<AuthGate> {
@override
Widget build(BuildContext context) {
if (_checking) {
return const SplashScreen();
return const LoginScreen();
}
if (_isLoggedIn) {
if (_hasMPin) {
@@ -214,7 +213,7 @@ class _AuthGateState extends State<AuthGate> {
future: _tryBiometric(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const SplashScreen();
return const LoginScreen();
}
if (snapshot.data == true) {
return const NavigationScaffold();
@@ -316,7 +315,22 @@ class _NavigationScaffoldState extends State<NavigationScaffold> {
int _selectedIndex = 0;
final List<Widget> _pages = [
const DashboardScreen(),
// const CardManagementScreen(),
BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) {
if (state is Authenticated) {
if (state.users.isNotEmpty) {
return AccountStatementScreen(
users: state.users,
selectedIndex: 0,
);
} else {
return const Center(child: Text("No accounts found."));
}
}
return const Center(child: CircularProgressIndicator());
},
),
const CardManagementScreen(),
const ServiceScreen(),
];
@@ -374,10 +388,14 @@ class _NavigationScaffoldState extends State<NavigationScaffold> {
icon: const Icon(Icons.home_filled),
label: AppLocalizations.of(context).home,
),
// BottomNavigationBarItem(
// icon: const Icon(Icons.credit_card),
// label: AppLocalizations.of(context).card,
// ),
BottomNavigationBarItem(
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,
@@ -422,7 +440,7 @@ class BiometricPromptScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
Future.microtask(() => _showDialog(context));
return const SplashScreen();
return const SizedBox.shrink();
}
Future<void> _showDialog(BuildContext context) async {

View File

@@ -1,3 +1,4 @@
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/branch_service.dart';
import 'package:kmobile/api/services/limit_service.dart';
import 'package:kmobile/api/services/rtgs_service.dart';
@@ -21,6 +22,8 @@ import '../security/secure_storage.dart';
final getIt = GetIt.instance;
Future<void> setupDependencies() async {
getIt.registerSingleton<RouteObserver<ModalRoute<void>>>(
RouteObserver<ModalRoute<void>>());
//getIt.registerLazySingleton<ThemeController>(() => ThemeController());
//getIt.registerLazySingleton<ThemeModeController>(() => ThemeModeController());
getIt.registerSingleton<ThemeCubit>(ThemeCubit());
@@ -71,9 +74,9 @@ Dio _createDioClient() {
final dio = Dio(
BaseOptions(
baseUrl:
// 'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com:8080', //test
//'http://lb-kccb-mobile-banking-app-848675342.ap-south-1.elb.amazonaws.com', //prod
'https://kccbmbnk.net', //prod small
'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com:8080', //test
//'http://lb-kccb-mobile-banking-app-848675342.ap-south-1.elb.amazonaws.com', //prod
//'https://kccbmbnk.net', //prod small
connectTimeout: const Duration(seconds: 60),
receiveTimeout: const Duration(seconds: 60),
headers: {

View File

@@ -18,16 +18,45 @@ class AccountInfoScreen extends StatefulWidget {
class _AccountInfoScreen extends State<AccountInfoScreen> {
late User selectedUser;
@override
void initState() {
super.initState();
selectedUser = widget.users[widget.selectedIndex];
}
String getFullAccountType(String? accountType) {
if (accountType == null || accountType.isEmpty) return 'N/A';
// Convert to title case
switch (accountType.toLowerCase()) {
case 'sa':
return AppLocalizations.of(context).savingsAccount;
case 'sb':
return AppLocalizations.of(context).savingsAccount;
case 'ln':
return AppLocalizations.of(context).loanAccount;
case 'td':
return AppLocalizations.of(context).termDeposit;
case 'rd':
return AppLocalizations.of(context).recurringDeposit;
case 'ca':
return "Current Account";
case 'cc':
return "Cash Credit Account";
case 'od':
return "Overdraft Account";
default:
return AppLocalizations.of(context).unknownAccount;
}
}
@override
Widget build(BuildContext context) {
final users = widget.users;
int selectedIndex = widget.selectedIndex;
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)
@@ -36,70 +65,106 @@ class _AccountInfoScreen extends State<AccountInfoScreen> {
),
body: Stack(
children: [
ListView(
padding: const EdgeInsets.all(16.0),
children: [
Text(
AppLocalizations.of(context).accountNumber,
style:
const TextStyle(fontWeight: FontWeight.w500, fontSize: 14),
),
DropdownButton<User>(
value: selectedUser,
onChanged: (User? newUser) {
if (newUser != null) {
setState(() {
selectedUser = newUser;
});
}
},
items: widget.users.map((user) {
return DropdownMenuItem<User>(
value: user,
child: Text(user.accountNo.toString()),
);
}).toList(),
),
InfoRow(
title: AppLocalizations.of(context).customerNumber,
value: selectedUser.cifNumber ?? 'N/A',
),
InfoRow(
title: AppLocalizations.of(context).productName,
value: selectedUser.productType ?? 'N/A',
),
// InfoRow(title: 'Account Opening Date', value: users[selectedIndex].accountOpeningDate ?? 'N/A'),
InfoRow(
title: AppLocalizations.of(context).accountStatus,
value: 'OPEN',
),
InfoRow(
title: AppLocalizations.of(context).availableBalance,
value: selectedUser.availableBalance ?? 'N/A',
),
InfoRow(
title: AppLocalizations.of(context).currentBalance,
value: selectedUser.currentBalance ?? 'N/A',
),
users[selectedIndex].approvedAmount != null
? InfoRow(
title: AppLocalizations.of(context).approvedAmount,
value: selectedUser.approvedAmount ?? 'N/A',
)
: const SizedBox.shrink(),
],
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Card(
elevation: 4,
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).accountNumber,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 18),
),
DropdownButton<User>(
value: selectedUser,
onChanged: (User? newUser) {
if (newUser != null) {
setState(() {
selectedUser = newUser;
});
}
},
items: widget.users.map((user) {
return DropdownMenuItem<User>(
value: user,
child: Text(
user.accountNo.toString(),
style: const TextStyle(
fontSize: 20, fontWeight: FontWeight.bold),
),
);
}).toList(),
isExpanded: true,
),
],
),
),
),
Expanded(
child: Card(
elevation: 4,
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: ListView(
children: [
InfoRow(
title: AppLocalizations.of(context).customerNumber,
value: selectedUser.cifNumber ?? 'N/A',
),
InfoRow(
title: AppLocalizations.of(context).accountType,
value: getFullAccountType(selectedUser.accountType),
),
InfoRow(
title: AppLocalizations.of(context).productName,
value: selectedUser.productType ?? 'N/A',
),
InfoRow(
title: AppLocalizations.of(context).accountStatus,
value: 'OPEN',
),
InfoRow(
title:
AppLocalizations.of(context).availableBalance,
value: selectedUser.availableBalance ?? 'N/A',
),
InfoRow(
title: AppLocalizations.of(context).currentBalance,
value: selectedUser.currentBalance ?? 'N/A',
),
if (users[selectedIndex].approvedAmount != null)
InfoRow(
title:
AppLocalizations.of(context).approvedAmount,
value: selectedUser.approvedAmount ?? 'N/A',
),
],
),
),
),
),
],
),
),
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
),
),
@@ -130,15 +195,18 @@ class InfoRow extends StatelessWidget {
Text(
title,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
fontSize: 18,
fontWeight: FontWeight.bold,
color: theme.colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 3),
Text(
value,
style: TextStyle(fontSize: 16, color: theme.colorScheme.onSurface),
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: theme.colorScheme.onSurface),
),
],
),

View File

@@ -14,15 +14,16 @@ 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:kmobile/data/models/user.dart';
class AccountStatementScreen extends StatefulWidget {
final String accountNo;
final String balance;
final String accountType;
final List<User> users;
final int selectedIndex;
const AccountStatementScreen({
super.key,
required this.accountNo,
required this.balance,
required this.accountType,
required this.users,
required this.selectedIndex,
});
@override
@@ -30,6 +31,7 @@ class AccountStatementScreen extends StatefulWidget {
}
class _AccountStatementScreen extends State<AccountStatementScreen> {
late User selectedUser;
DateTime? fromDate;
DateTime? toDate;
bool _txLoading = true;
@@ -41,6 +43,7 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
@override
void initState() {
super.initState();
selectedUser = widget.users[widget.selectedIndex];
_loadTransactions();
}
@@ -52,7 +55,7 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
try {
final repo = getIt<TransactionRepository>();
final txs = await repo.fetchTransactions(
widget.accountNo,
selectedUser.accountNo ?? '',
fromDate: fromDate,
toDate: toDate,
);
@@ -97,12 +100,15 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
return;
}
final now = DateTime.now();
final maxToDate = fromDate!.add(const Duration(days: 31)).isBefore(now)
? fromDate!.add(const Duration(days: 31))
final maxToDate = fromDate!.add(const Duration(days: 183)).isBefore(now)
? fromDate!.add(const Duration(days: 183))
: now;
final initialToDate = toDate ?? now;
final clampedInitialToDate =
initialToDate.isBefore(fromDate!) ? fromDate! : initialToDate;
final picked = await showDatePicker(
context: context,
initialDate: toDate ?? fromDate!,
initialDate: clampedInitialToDate,
firstDate: fromDate!,
lastDate: maxToDate,
);
@@ -140,18 +146,27 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
"${AppLocalizations.of(context).accountNumber}: ",
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
),
),
Text(widget.accountNo,
style: const TextStyle(fontSize: 17)),
],
Text(
AppLocalizations.of(context).accountNumber,
style: const TextStyle(
fontWeight: FontWeight.w500, fontSize: 14),
),
DropdownButton<User>(
value: selectedUser,
onChanged: (User? newUser) {
if (newUser != null) {
setState(() {
selectedUser = newUser;
});
_loadTransactions();
}
},
items: widget.users.map((user) {
return DropdownMenuItem<User>(
value: user,
child: Text(user.accountNo.toString()),
);
}).toList(),
),
const SizedBox(height: 15),
Row(
@@ -162,7 +177,7 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
fontSize: 17,
),
),
Text('${widget.balance}',
Text('${selectedUser.availableBalance}',
style: const TextStyle(fontSize: 17)),
],
),
@@ -236,8 +251,11 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
itemCount: 3,
itemBuilder: (_, __) => ListTile(
leading: Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
baseColor:
Theme.of(context).colorScheme.surfaceVariant,
highlightColor: Theme.of(context)
.colorScheme
.onSurfaceVariant,
child: CircleAvatar(
radius: 12,
backgroundColor:
@@ -245,8 +263,11 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
),
),
title: Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
baseColor:
Theme.of(context).colorScheme.surfaceVariant,
highlightColor: Theme.of(context)
.colorScheme
.onSurfaceVariant,
child: Container(
height: 10,
width: 100,
@@ -255,8 +276,11 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
),
),
subtitle: Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
baseColor:
Theme.of(context).colorScheme.surfaceVariant,
highlightColor: Theme.of(context)
.colorScheme
.onSurfaceVariant,
child: Container(
height: 8,
width: 60,
@@ -286,7 +310,7 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
? Symbols.call_received
: Symbols.call_made,
color: tx.type == 'CR'
? Colors.green
? const Color(0xFF10BB10)
: Theme.of(context).colorScheme.error,
),
title: Text(
@@ -330,7 +354,8 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
);
},
separatorBuilder: (context, index) {
return const Divider();
return Divider(
color: Theme.of(context).dividerColor);
},
),
),
@@ -398,10 +423,10 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [
pw.Text('Account Number: ${widget.accountNo}',
pw.Text('Account Number: ${selectedUser.accountNo}',
style:
pw.TextStyle(font: pw.Font.ttf(rubik), fontSize: 15)),
pw.Text('Account Type: ${widget.accountType}',
pw.Text('Account Type: ${selectedUser.productType}',
style: pw.TextStyle(
fontSize: 15,
font: pw.Font.ttf(rubik),

View File

@@ -0,0 +1,142 @@
import 'package:flutter/material.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
class AllAccountsScreen extends StatefulWidget {
final List<User> users;
const AllAccountsScreen({super.key, required this.users});
@override
State<AllAccountsScreen> createState() => _AllAccountsScreenState();
}
class _AllAccountsScreenState extends State<AllAccountsScreen> {
final Map<String, bool> _visibilityMap = {};
String getFullAccountType(BuildContext context, String? accountType) {
// This is duplicated from dashboard_screen.dart.
// In a real app, this should be moved to a utility/helper class.
if (accountType == null || accountType.isEmpty) return 'N/A';
switch (accountType.toLowerCase()) {
case 'sa':
return "Savings Account"; // Using hardcoded strings for simplicity
case 'sb':
return "Savings Account";
case 'ln':
return "Loan Account";
case 'td':
return "Term Deposit";
case 'rd':
return "Recurring Deposit";
case 'ca':
return "Current Account";
default:
return "Unknown Account";
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('All Accounts'),
),
body: ListView.builder(
itemCount: widget.users.length,
itemBuilder: (context, index) {
final user = widget.users[index];
return Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: _buildAccountCard(user),
);
},
),
);
}
Widget _buildAccountCard(User user) {
final theme = Theme.of(context);
final accountNo = user.accountNo ?? '';
final isVisible = _visibilityMap[accountNo] ?? false;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
decoration: BoxDecoration(
color: const Color(0xFF01A04C),
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Top section: Account Type and Number
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
getFullAccountType(context, user.accountType),
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
Text(
user.accountNo ?? 'N/A',
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: 14,
fontWeight: FontWeight.w700,
),
),
],
),
const SizedBox(height: 16),
// Bottom section: Balance and Toggle
Row(
children: [
Text(
"",
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: 32,
fontWeight: FontWeight.w700,
),
),
Expanded(
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(
isVisible ? user.currentBalance ?? '0.00' : '*****',
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: 32,
fontWeight: FontWeight.w700,
),
),
),
),
const Spacer(),
InkWell(
onTap: () {
setState(() {
_visibilityMap[accountNo] = !isVisible;
});
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(
isVisible ? Symbols.visibility_lock : Symbols.visibility,
color: theme.scaffoldBackgroundColor,
weight: 800,
),
),
),
],
),
],
),
);
}
}

View File

@@ -42,7 +42,9 @@ class TransactionDetailsScreen extends StatelessWidget {
isCredit
? Symbols.call_received
: Symbols.call_made,
color: isCredit ? Colors.green : Colors.red,
color: isCredit
? const Color(0xFF10BB10)
: Theme.of(context).colorScheme.error,
size: 28,
),
],
@@ -51,9 +53,9 @@ class TransactionDetailsScreen extends StatelessWidget {
// Date centered
Text(
transaction.date ?? "",
style: const TextStyle(
style: TextStyle(
fontSize: 16,
color: Colors.grey,
color: Theme.of(context).textTheme.bodySmall?.color,
),
textAlign: TextAlign.center,
),
@@ -61,7 +63,7 @@ class TransactionDetailsScreen extends StatelessWidget {
),
),
),
const Divider(),
Divider(color: Theme.of(context).dividerColor),
Expanded(
flex: 5,
child: ListView(

View File

@@ -22,7 +22,9 @@ class BeneficiaryDetailsScreen extends StatelessWidget {
_showSuccessDialog(context);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('${AppLocalizations.of(context).failedToDeleteBeneficiary} : $e')),
SnackBar(
content: Text(
'${AppLocalizations.of(context).failedToDeleteBeneficiary} : $e')),
);
}
}
@@ -33,7 +35,8 @@ class BeneficiaryDetailsScreen extends StatelessWidget {
builder: (BuildContext context) {
return AlertDialog(
title: Text(AppLocalizations.of(context).success),
content: Text(AppLocalizations.of(context).beneficiaryDeletedSuccessfully),
content:
Text(AppLocalizations.of(context).beneficiaryDeletedSuccessfully),
actions: <Widget>[
TextButton(
child: Text(AppLocalizations.of(context).ok),
@@ -53,8 +56,8 @@ class BeneficiaryDetailsScreen extends StatelessWidget {
builder: (BuildContext context) {
return AlertDialog(
title: Text(AppLocalizations.of(context).deleteBeneficiary),
content:
Text(AppLocalizations.of(context).areYouSureYouWantToDeleteThisBeneficiary),
content: Text(AppLocalizations.of(context)
.areYouSureYouWantToDeleteThisBeneficiary),
actions: <Widget>[
TextButton(
child: Text(AppLocalizations.of(context).cancel),

View File

@@ -21,21 +21,47 @@ class _ManageBeneficiariesScreen extends State<ManageBeneficiariesScreen> {
var service = getIt<BeneficiaryService>();
bool _isLoading = true;
List<Beneficiary> _beneficiaries = [];
List<Beneficiary> _filteredBeneficiaries = [];
final TextEditingController _searchController = TextEditingController();
@override
void initState() {
super.initState();
_loadBeneficiaries();
_searchController.addListener(() {
_filterBeneficiaries(_searchController.text);
});
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
Future<void> _loadBeneficiaries() async {
final data = await service.fetchBeneficiaryList();
setState(() {
_beneficiaries = data;
_filteredBeneficiaries = data;
_isLoading = false;
});
}
void _filterBeneficiaries(String query) {
setState(() {
if (query.isEmpty) {
_filteredBeneficiaries = _beneficiaries;
} else {
_filteredBeneficiaries = _beneficiaries.where((beneficiary) {
final lowerQuery = query.toLowerCase();
return beneficiary.name.toLowerCase().contains(lowerQuery) ||
beneficiary.accountNo.toLowerCase().contains(lowerQuery);
}).toList();
}
});
}
Widget _buildShimmerList() {
return ListView.builder(
itemCount: 6,
@@ -63,40 +89,43 @@ class _ManageBeneficiariesScreen extends State<ManageBeneficiariesScreen> {
}
Widget _buildBeneficiaryList() {
if (_beneficiaries.isEmpty) {
if (_filteredBeneficiaries.isEmpty) {
return Center(
child: Text(AppLocalizations.of(context).noBeneficiaryFound));
}
return ListView.builder(
itemCount: _beneficiaries.length,
itemCount: _filteredBeneficiaries.length,
itemBuilder: (context, index) {
final item = _beneficiaries[index];
return ListTile(
leading: CircleAvatar(
radius: 24,
backgroundColor: Colors.transparent,
child: getBankLogo(item.bankName, context),
),
title: Text(item.name),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.accountNo),
if (item.bankName != null && item.bankName!.isNotEmpty)
Text(
item.bankName!,
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
final item = _filteredBeneficiaries[index];
return Card(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: ListTile(
leading: CircleAvatar(
radius: 24,
backgroundColor: Colors.transparent,
child: getBankLogo(item.bankName, context),
),
title: Text(item.name),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.accountNo),
if (item.bankName != null && item.bankName!.isNotEmpty)
Text(
item.bankName!,
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
],
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => BeneficiaryDetailsScreen(beneficiary: item),
),
],
);
},
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => BeneficiaryDetailsScreen(beneficiary: item),
),
);
},
);
},
);
@@ -111,7 +140,27 @@ class _ManageBeneficiariesScreen extends State<ManageBeneficiariesScreen> {
),
body: Stack(
children: [
_isLoading ? _buildShimmerList() : _buildBeneficiaryList(),
Column(
children: [
Padding(
padding: const EdgeInsets.all(12.0),
child: TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: "Search by name or account number",
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
Expanded(
child:
_isLoading ? _buildShimmerList() : _buildBeneficiaryList(),
),
],
),
IgnorePointer(
child: Center(
child: Opacity(

View File

@@ -33,20 +33,6 @@ class CardDetailsScreen extends StatelessWidget {
],
),
),
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
),
),
),
),
),
],
),
);
@@ -105,7 +91,7 @@ class CardTile extends StatelessWidget {
"Kangra Central Co-operative Bank",
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontSize: 15.5,
fontWeight: FontWeight.bold,
),
overflow: TextOverflow.ellipsis,

View File

@@ -35,7 +35,7 @@ class _CardManagementScreen extends State<CardManagementScreen> {
onTap: () {},
disabled: true, // Add this
),
const Divider(height: 1),
Divider(height: 1, color: Theme.of(context).dividerColor),
CardManagementTile(
icon: Symbols.remove_moderator,
label: AppLocalizations.of(context).blockUnblockCard,
@@ -49,7 +49,7 @@ class _CardManagementScreen extends State<CardManagementScreen> {
},
disabled: true,
),
const Divider(height: 1),
Divider(height: 1, color: Theme.of(context).dividerColor),
CardManagementTile(
icon: Symbols.password_2,
label: AppLocalizations.of(context).changeCardPin,
@@ -63,7 +63,7 @@ class _CardManagementScreen extends State<CardManagementScreen> {
},
disabled: true,
),
const Divider(height: 1),
Divider(height: 1, color: Theme.of(context).dividerColor),
CardManagementTile(
icon: Symbols.payment_card,
label: AppLocalizations.of(context).viewCardDeatils,
@@ -75,9 +75,9 @@ class _CardManagementScreen extends State<CardManagementScreen> {
),
);
},
disabled: true,
disabled: false,
),
const Divider(height: 1),
Divider(height: 1, color: Theme.of(context).dividerColor),
],
),
IgnorePointer(

View File

@@ -17,8 +17,9 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
appBar: AppBar(
title: Text(
AppLocalizations.of(context).chequeManagement,
style:
const TextStyle(color: Colors.black, fontWeight: FontWeight.w500),
style: TextStyle(
color: Theme.of(context).textTheme.titleLarge?.color,
fontWeight: FontWeight.w500),
),
centerTitle: false,
),
@@ -32,7 +33,7 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
label: AppLocalizations.of(context).requestChequeBook,
onTap: () {},
),
const Divider(height: 1),
Divider(height: 1, color: Theme.of(context).dividerColor),
ChequeManagementTile(
icon: Symbols.data_alert,
label: AppLocalizations.of(context).enquiry,
@@ -44,31 +45,31 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
);
},
),
const Divider(height: 1),
Divider(height: 1, color: Theme.of(context).dividerColor),
ChequeManagementTile(
icon: Symbols.approval_delegation,
label: AppLocalizations.of(context).chequeDeposit,
onTap: () {},
),
const Divider(height: 1),
Divider(height: 1, color: Theme.of(context).dividerColor),
ChequeManagementTile(
icon: Symbols.front_hand,
label: AppLocalizations.of(context).stopCheque,
onTap: () {},
),
const Divider(height: 1),
Divider(height: 1, color: Theme.of(context).dividerColor),
ChequeManagementTile(
icon: Symbols.cancel_presentation,
label: AppLocalizations.of(context).revokeStop,
onTap: () {},
),
const Divider(height: 1),
Divider(height: 1, color: Theme.of(context).dividerColor),
ChequeManagementTile(
icon: Symbols.payments,
label: AppLocalizations.of(context).positivePay,
onTap: () {},
),
const Divider(height: 1),
Divider(height: 1, color: Theme.of(context).dividerColor),
],
),
IgnorePointer(

View File

@@ -73,7 +73,7 @@ class _CustomerInfoScreenState extends State<CustomerInfoScreen> {
const SizedBox(height: 30),
InfoField(
label: AppLocalizations.of(context).activeAccounts,
value: user.activeAccounts?.toString() ?? '6',
value: user.activeAccounts?.toString() ?? 'N/A',
),
InfoField(
label: AppLocalizations.of(context).mobileNumber,
@@ -91,7 +91,7 @@ class _CustomerInfoScreenState extends State<CustomerInfoScreen> {
value: user.branchId ?? 'N/A',
),
InfoField(
label: AppLocalizations.of(context).branchAddress,
label: AppLocalizations.of(context).address,
value: user.address ?? 'N/A',
), // Replace with Aadhar if available
InfoField(

View File

@@ -1,15 +1,15 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:kmobile/data/repositories/transaction_repository.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/features/accounts/screens/account_info_screen.dart';
import 'package:kmobile/features/accounts/screens/account_statement_screen.dart';
import 'package:kmobile/features/accounts/screens/transaction_details_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/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';
@@ -21,7 +21,6 @@ import 'package:local_auth/local_auth.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:shimmer/shimmer.dart';
import 'package:kmobile/data/models/transaction.dart';
import '../../../l10n/app_localizations.dart';
class DashboardScreen extends StatefulWidget {
@@ -32,47 +31,233 @@ class DashboardScreen extends StatefulWidget {
}
class _DashboardScreenState extends State<DashboardScreen>
with SingleTickerProviderStateMixin {
with SingleTickerProviderStateMixin, RouteAware {
int selectedAccountIndex = 0;
bool isVisible = false;
Map<String, bool> _visibilityMap = {};
bool isRefreshing = false;
bool isBalanceLoading = false;
bool _biometricPromptShown = false;
bool _txLoading = false;
List<Transaction> _transactions = [];
bool _txInitialized = false;
Future<void> _loadTransactions(String accountNo) async {
bool _biometricPromptShown = false;
bool _txInitialized = false;
PageController? _pageController;
final routeObserver = getIt<RouteObserver<ModalRoute<void>>>();
@override
void didChangeDependencies() {
super.didChangeDependencies();
routeObserver.subscribe(this, ModalRoute.of(context)!);
}
@override
void dispose() {
routeObserver.unsubscribe(this);
_pageController?.dispose();
_visibilityMap.clear();
super.dispose();
}
@override
void didPushNext() {
// This method is called when another route is pushed on top of this one.
// We clear the map and call setState to ensure the UI is updated
// if the user navigates back.
setState(() {
_txLoading = true;
_transactions = [];
_visibilityMap.clear();
});
try {
final repo = getIt<TransactionRepository>();
final txs = await repo.fetchTransactions(accountNo);
var fiveTxns = <Transaction>[];
//only take the first 5 transactions
if (txs.length > 5) {
fiveTxns = txs.sublist(0, 5);
} else {
fiveTxns = txs;
}
setState(() => _transactions = fiveTxns);
} catch (e) {
log(accountNo, error: e);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context).failedToLoad(e.toString()),
}
Widget _buildViewAllTab(List<User> users) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AllAccountsScreen(users: users),
),
);
},
child: Container(
width: 40, // Small width for the tab
height: 160,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant,
),
child: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"View",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
Text(
"All",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
Icon(
Icons.arrow_forward_ios,
size: 16,
),
],
),
),
);
}
Widget _buildAccountCard(User user, bool isSelected) {
final theme = Theme.of(context);
final bool isCardVisible = _visibilityMap[user.accountNo] ?? false;
// Animated scale for the selected card
final scale = isSelected ? 1.0 : 0.85;
return AnimatedScale(
duration: const Duration(milliseconds: 200),
scale: scale,
child: Transform.translate(
offset: isSelected ? const Offset(10.0, 0.0) : Offset.zero,
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),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Top section with account type and number (no refresh button here)
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
getFullAccountType(user.accountType),
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
Text(
user.accountNo ?? 'N/A',
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: 14,
fontWeight: FontWeight.w700,
),
overflow: TextOverflow.ellipsis,
),
],
),
),
if (isSelected) // Show logo only if card is selected
CircleAvatar(
radius: 20,
backgroundColor: Colors.white,
child: Padding(
padding: const EdgeInsets.all(2.0),
child: ClipOval(
child: Image.asset(
'assets/images/logo.png',
width: 30,
height: 30,
fit: BoxFit.cover,
),
),
),
),
],
),
const Spacer(),
// Bottom section with balance and combined toggle/refresh
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
if (isRefreshing && isSelected)
Expanded(child: _buildBalanceShimmer())
else
Expanded(
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Row(
children: [
Text(
"",
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: 40,
fontWeight: FontWeight.w700,
),
),
Text(
isCardVisible
? user.currentBalance ?? '0.00'
: '*****',
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: 40,
fontWeight: FontWeight.w700,
),
),
],
),
),
),
const SizedBox(width: 10), // A steady space
if (isSelected) // Only show toggle for selected card
InkWell(
onTap: () async {
if (isRefreshing)
return; // Prevent taps while refreshing
final accountNo = user.accountNo;
if (accountNo == null) return;
final bool currentVisibility =
_visibilityMap[accountNo] ?? false;
if (!currentVisibility) {
// If hidden, refresh data and then show the balance
await _refreshAccountData(context);
if (mounted) {
setState(() {
_visibilityMap[accountNo] = true;
});
}
} else {
// If visible, just hide it
setState(() {
_visibilityMap[accountNo] = false;
});
}
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(
isCardVisible
? Symbols.visibility_lock
: Symbols.visibility,
color: theme.scaffoldBackgroundColor,
weight: 800,
),
),
),
],
),
const Spacer(),
],
),
),
);
} finally {
if (mounted) {
setState(() => _txLoading = false);
}
}
),
);
}
Future<void> _refreshAccountData(BuildContext context) async {
@@ -102,8 +287,7 @@ class _DashboardScreenState extends State<DashboardScreen>
return Shimmer.fromColors(
baseColor: theme.colorScheme.primary,
highlightColor: theme.colorScheme.onPrimary,
child: Container(
width: 200, height: 42, color: theme.scaffoldBackgroundColor),
child: Container(height: 36, color: theme.scaffoldBackgroundColor),
);
}
@@ -150,6 +334,10 @@ class _DashboardScreenState extends State<DashboardScreen>
return AppLocalizations.of(context).recurringDeposit;
case 'ca':
return "Current Account";
case 'cc':
return "Cash Credit Account";
case 'od':
return "Overdraft Account";
default:
return AppLocalizations.of(context).unknownAccount;
}
@@ -222,20 +410,27 @@ class _DashboardScreenState extends State<DashboardScreen>
onTap: () {
final authState = context.read<AuthCubit>().state;
String mobileNumberToPass = '';
String customerNo = '';
String customerName = '';
if (authState is Authenticated) {
if (selectedAccountIndex >= 0 &&
selectedAccountIndex < authState.users.length) {
mobileNumberToPass =
authState.users[selectedAccountIndex].mobileNo ?? '';
customerNo =
authState.users[selectedAccountIndex].cifNumber ?? '';
customerName =
authState.users[selectedAccountIndex].name ?? '';
}
}
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ProfileScreen(mobileNumber: mobileNumberToPass),
builder: (context) => ProfileScreen(
mobileNumber: mobileNumberToPass,
customerNo: customerNo,
customerName: customerName),
),
);
},
@@ -277,13 +472,15 @@ class _DashboardScreenState extends State<DashboardScreen>
if (!_txInitialized) {
_txInitialized = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadTransactions(currAccount.accountNo!);
});
}
_pageController ??= PageController(
initialPage: selectedAccountIndex,
viewportFraction: 0.80,
);
final firstName = getProcessedFirstName(currAccount.name);
return SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
@@ -302,162 +499,58 @@ class _DashboardScreenState extends State<DashboardScreen>
),
const SizedBox(height: 16),
// Account Info Card
Container(
padding: const EdgeInsets.symmetric(
horizontal: 18,
vertical: 10,
),
decoration: BoxDecoration(
color: Color(0xFF01A04C),
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
"${getFullAccountType(currAccount.accountType)}: ",
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
DropdownButton<int>(
value: selectedAccountIndex,
dropdownColor: theme.colorScheme.primary,
underline: const SizedBox(),
icon: const Icon(Icons.keyboard_arrow_down),
iconEnabledColor: theme.colorScheme.onPrimary,
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: 18,
),
items: List.generate(users.length, (index) {
return DropdownMenuItem<int>(
value: index,
child: Text(
users[index].accountNo ?? 'N/A',
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
);
}),
onChanged: (int? newIndex) async {
if (newIndex == null ||
newIndex == selectedAccountIndex) {
return;
}
if (isBalanceLoading) return;
if (isVisible) {
setState(() {
isBalanceLoading = true;
selectedAccountIndex = newIndex;
});
await Future.delayed(
const Duration(milliseconds: 200),
);
setState(() {
isBalanceLoading = false;
});
} else {
// Account Info Cards
ClipRect(
child: SizedBox(
// This SizedBox defines the height for the Stack
height: 160,
child: Stack(
children: [
// PageView part, painted underneath
Padding(
padding: const EdgeInsets.only(
left: 48.0), // Space for tab (40) + gap (8)
child: SizedBox(
// Keep SizedBox for PageView height
height: 160,
child: PageView.builder(
controller: _pageController,
itemCount: users.length,
clipBehavior: Clip
.none, // Keep this to show adjacent cards
padEnds: false,
onPageChanged: (int newIndex) async {
if (newIndex == selectedAccountIndex)
return;
// Hide the balance of the old card when scrolling away
final oldAccountNo =
users[selectedAccountIndex].accountNo;
if (oldAccountNo != null) {
_visibilityMap[oldAccountNo] = false;
}
setState(() {
selectedAccountIndex = newIndex;
});
}
await _loadTransactions(
users[newIndex].accountNo!,
);
},
),
const Spacer(),
IconButton(
icon: isRefreshing
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
color: theme.colorScheme.onPrimary,
strokeWidth: 2,
),
)
: Icon(
Symbols.refresh,
color: theme.colorScheme.onPrimary,
),
onPressed: isRefreshing
? null
: () => _refreshAccountData(context),
tooltip: 'Refresh',
),
],
),
const SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"",
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: 40,
fontWeight: FontWeight.w700,
},
itemBuilder: (context, index) {
final user = users[index];
final isSelected =
index == selectedAccountIndex;
return _buildAccountCard(
user, isSelected);
},
),
),
isRefreshing || isBalanceLoading
? _buildBalanceShimmer()
: Text(
isVisible
? currAccount.currentBalance ??
'0.00'
: '*****',
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: 40,
fontWeight: FontWeight.w700,
),
),
const Spacer(),
InkWell(
onTap: () async {
if (isBalanceLoading) return;
if (!isVisible) {
setState(() {
isBalanceLoading = true;
});
await Future.delayed(
const Duration(seconds: 1),
);
setState(() {
isVisible = true;
isBalanceLoading = false;
});
} else {
setState(() {
isVisible = false;
});
}
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(
isVisible
? Symbols.visibility_lock
: Symbols.visibility,
color: theme.scaffoldBackgroundColor,
weight: 800,
),
),
),
],
),
const SizedBox(height: 15),
],
),
// View All tab part, painted on top
Align(
alignment: Alignment.centerLeft,
child: _buildViewAllTab(users),
),
],
),
),
),
const SizedBox(height: 18),
@@ -465,13 +558,16 @@ class _DashboardScreenState extends State<DashboardScreen>
AppLocalizations.of(context).quickLinks,
style: const TextStyle(fontSize: 17),
),
const SizedBox(height: 16),
const SizedBox(height: 24),
// Quick Links
GridView.count(
crossAxisCount: 4,
crossAxisCount: 3,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisSpacing: 10,
mainAxisSpacing: 10,
childAspectRatio: 1,
children: [
_buildQuickLink(
Symbols.id_card,
@@ -539,13 +635,8 @@ class _DashboardScreenState extends State<DashboardScreen>
MaterialPageRoute(
builder: (context) =>
AccountStatementScreen(
accountNo: users[selectedAccountIndex]
.accountNo!,
balance: users[selectedAccountIndex]
.availableBalance!,
accountType:
users[selectedAccountIndex]
.accountType!,
users: users,
selectedIndex: selectedAccountIndex,
)));
}),
_buildQuickLink(Icons.location_pin, "Branch Locator",
@@ -574,69 +665,22 @@ class _DashboardScreenState extends State<DashboardScreen>
builder: (context) =>
const EnquiryScreen()));
}),
],
),
const SizedBox(height: 5),
// Recent Transactions
Text(
AppLocalizations.of(context).recentTransactions,
style: const TextStyle(fontSize: 17),
),
const SizedBox(height: 16),
if (_txLoading)
..._buildTransactionShimmer()
else if (_transactions.isNotEmpty)
..._transactions.map(
(tx) => ListTile(
leading: Icon(
tx.type == 'CR'
? Symbols.call_received
: Symbols.call_made,
color: tx.type == 'CR'
? const Color(0xFF10BB10)
: theme.colorScheme.error,
),
title: Text(
tx.date ?? '',
style: const TextStyle(fontSize: 15),
),
subtitle: Text(
tx.name != null
? (tx.name!.length > 22
? tx.name!.substring(0, 22)
: tx.name!)
: '',
style: const TextStyle(fontSize: 12),
),
trailing: Text(
"${tx.amount}",
style: const TextStyle(fontSize: 17),
),
onTap: () {
_buildQuickLink(
Symbols.request_quote,
AppLocalizations.of(context).chequeManagement,
() {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
TransactionDetailsScreen(transaction: tx),
builder: (context) =>
const ChequeManagementScreen(),
),
);
},
disable: true,
),
)
else
Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
child: Center(
child: Text(
AppLocalizations.of(context).noTransactions,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.outline,
),
),
),
),
],
),
],
),
),
@@ -684,27 +728,35 @@ class _DashboardScreenState extends State<DashboardScreen>
bool disable = false,
}) {
final theme = Theme.of(context);
return InkWell(
onTap: disable ? null : onTap,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
size: 30,
color: disable
? theme.colorScheme.onSurface.withOpacity(0.3)
: theme.colorScheme.primary,
grade: 200,
weight: 700,
),
const SizedBox(height: 4),
Text(
label,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 13),
),
],
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
child: InkWell(
onTap: disable ? null : onTap,
borderRadius: BorderRadius.circular(12.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
size: 30,
color: disable ? theme.disabledColor : theme.colorScheme.primary,
),
const SizedBox(height: 4),
Text(
label,
textAlign: TextAlign.center,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
fontSize: 12,
color:
disable ? theme.disabledColor : theme.colorScheme.onSurface,
),
),
],
),
),
);
}

View File

@@ -53,25 +53,54 @@ class _EnquiryScreen extends State<EnquiryScreen> {
}
Widget _buildContactItem(String role, String email, String phone) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(role,
style: TextStyle(color: Theme.of(context).colorScheme.onSurface)),
const SizedBox(height: 4),
GestureDetector(
onTap: () => _launchEmailAddress(email),
child: Text(email,
style: TextStyle(color: Theme.of(context).colorScheme.primary)),
return Card(
elevation: 4,
margin: const EdgeInsets.symmetric(vertical: 8),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(role,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
fontSize: 15,
fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
GestureDetector(
onTap: () => _launchEmailAddress(email),
child: Row(
children: [
const Icon(Icons.email),
const SizedBox(width: 8),
Expanded(
child: Text(email,
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontSize: 14)),
),
],
),
),
const SizedBox(height: 8),
GestureDetector(
onTap: () => _launchPhoneNumber(phone),
child: Row(
children: [
const Icon(Icons.phone),
const SizedBox(width: 8),
Expanded(
child: Text(phone,
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontSize: 14)),
),
],
),
),
],
),
const SizedBox(height: 4),
GestureDetector(
onTap: () => _launchPhoneNumber(phone),
child: Text(phone,
style:
TextStyle(color: Theme.of(context).colorScheme.primary)), // Changed color for visibility
),
],
),
);
}
@@ -89,60 +118,67 @@ class _EnquiryScreen extends State<EnquiryScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 20),
GestureDetector(
onTap: () => _launchUrl("https://kccbhp.bank.in/complaint-form/"),
child: Row(mainAxisSize: MainAxisSize.min, children: [
Text(
"Complaint Form",
style: TextStyle(
fontSize: 17,
color: Theme.of(context).colorScheme.primary,
decoration: TextDecoration.underline, // Added underline for link clarity
decorationColor:
Theme.of(context).colorScheme.primary,
),
Card(
elevation: 4,
child: InkWell(
onTap: () =>
_launchUrl("https://kccbhp.bank.in/complaint-form/"),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Complaint Form",
style: TextStyle(
fontSize: 15,
color: Theme.of(context).colorScheme.primary,
),
),
Icon(
Icons.open_in_new,
color: Theme.of(context).colorScheme.primary,
size: 16.0,
),
],
),
const SizedBox(width: 4),
Icon(
Icons.open_in_new,
color: Theme.of(context).colorScheme.primary,
size: 16.0,
),
])),
const SizedBox(height: 40),
),
),
),
const Divider(height: 32),
Text(
AppLocalizations.of(context).keyContacts,
style: TextStyle(
fontSize: 17,
color: Theme.of(context).colorScheme.primary,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
),
// horizontal line
),
Divider(color: Theme.of(context).colorScheme.outline),
const SizedBox(height: 16),
_buildContactItem(
AppLocalizations.of(context).chairman,
"chairman@kccb.in",
"01892-222677",
),
const SizedBox(height: 16),
_buildContactItem(
AppLocalizations.of(context).managingDirector,
"md@kccb.in",
"01892-224969",
),
const SizedBox(height: 16),
_buildContactItem(
AppLocalizations.of(context).gmWest,
"gmw@kccb.in",
"01892-223280",
),
const SizedBox(height: 16),
_buildContactItem(
AppLocalizations.of(context).gmNorth,
"gmn@kccb.in",
"01892-224607",
Expanded(
child: ListView(
children: [
_buildContactItem(
AppLocalizations.of(context).chairman,
"chairman@kccb.in",
"01892-222677",
),
_buildContactItem(
AppLocalizations.of(context).managingDirector,
"md@kccb.in",
"01892-224969",
),
_buildContactItem(
AppLocalizations.of(context).gmWest,
"gmw@kccb.in",
"01892-223280",
),
_buildContactItem(
AppLocalizations.of(context).gmNorth,
"gmn@kccb.in",
"01892-224607",
),
],
),
),
],
),
@@ -165,4 +201,4 @@ class _EnquiryScreen extends State<EnquiryScreen> {
),
);
}
}
}

View File

@@ -492,7 +492,8 @@ class _FundTransferAmountScreenState extends State<FundTransferAmountScreen> {
},
),
const SizedBox(height: 8),
if (_isLoadingLimit) Text(AppLocalizations.of(context).fetchingDailyLimit),
if (_isLoadingLimit)
Text(AppLocalizations.of(context).fetchingDailyLimit),
if (!_isLoadingLimit && _limit != null)
Text(
'Remaining Daily Limit: ${_formatCurrency.format(_limit!.dailyLimit - _limit!.usedLimit)}',

View File

@@ -28,11 +28,22 @@ class _FundTransferBeneficiaryScreenState
var service = getIt<BeneficiaryService>();
bool _isLoading = true;
List<Beneficiary> _beneficiaries = [];
List<Beneficiary> _filteredBeneficiaries = [];
final TextEditingController _searchController = TextEditingController();
@override
void initState() {
super.initState();
_loadBeneficiaries();
_searchController.addListener(() {
_filterBeneficiaries(_searchController.text);
});
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
Future<void> _loadBeneficiaries() async {
@@ -43,10 +54,25 @@ class _FundTransferBeneficiaryScreenState
? b.bankName!.toLowerCase().contains('kangra central')
: !b.bankName!.toLowerCase().contains('kangra central'))
.toList();
_filteredBeneficiaries = _beneficiaries;
_isLoading = false;
});
}
void _filterBeneficiaries(String query) {
setState(() {
if (query.isEmpty) {
_filteredBeneficiaries = _beneficiaries;
} else {
_filteredBeneficiaries = _beneficiaries.where((beneficiary) {
final lowerQuery = query.toLowerCase();
return beneficiary.name.toLowerCase().contains(lowerQuery) ||
beneficiary.accountNo.toLowerCase().contains(lowerQuery);
}).toList();
}
});
}
Widget _buildShimmerList() {
return ListView.builder(
itemCount: 6,
@@ -74,14 +100,14 @@ class _FundTransferBeneficiaryScreenState
}
Widget _buildBeneficiaryList() {
if (_beneficiaries.isEmpty) {
if (_filteredBeneficiaries.isEmpty) {
return Center(
child: Text(AppLocalizations.of(context).noBeneficiaryFound));
}
return ListView.builder(
itemCount: _beneficiaries.length,
itemCount: _filteredBeneficiaries.length,
itemBuilder: (context, index) {
final beneficiary = _beneficiaries[index];
final beneficiary = _filteredBeneficiaries[index];
// --- Cooldown Logic ---
bool isCoolingDown = false;
@@ -94,60 +120,63 @@ class _FundTransferBeneficiaryScreenState
// By wrapping the ListTile in an Opacity widget, we can make it look
// disabled while ensuring the onTap callback still works.
return Opacity(
opacity: isCoolingDown ? 0.5 : 1.0,
child: ListTile(
// REMOVED the 'enabled' property from here.
leading: CircleAvatar(
radius: 24,
backgroundColor: Colors.transparent,
child: getBankLogo(beneficiary.bankName, context),
),
title: Text(beneficiary.name),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(beneficiary.accountNo),
if (beneficiary.bankName != null &&
beneficiary.bankName!.isNotEmpty)
Text(
beneficiary.bankName!,
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
],
),
trailing: isCoolingDown
? CooldownTimer(
createdAt: beneficiary.createdAt!,
onTimerFinish: () {
setState(() {});
},
)
: null,
onTap: () {
if (isCoolingDown) {
// This will now execute correctly on tap
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Beneficiary will be enabled after the cooldown period.'),
behavior: SnackBarBehavior.floating,
),
);
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FundTransferAmountScreen(
debitAccountNo: widget.creditAccountNo,
creditBeneficiary: beneficiary,
remitterName: widget.remitterName,
isOwnBank: widget.isOwnBank,
return Card(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: Opacity(
opacity: isCoolingDown ? 0.5 : 1.0,
child: ListTile(
// REMOVED the 'enabled' property from here.
leading: CircleAvatar(
radius: 24,
backgroundColor: Colors.transparent,
child: getBankLogo(beneficiary.bankName, context),
),
title: Text(beneficiary.name),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(beneficiary.accountNo),
if (beneficiary.bankName != null &&
beneficiary.bankName!.isNotEmpty)
Text(
beneficiary.bankName!,
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
),
);
}
},
],
),
trailing: isCoolingDown
? CooldownTimer(
createdAt: beneficiary.createdAt!,
onTimerFinish: () {
setState(() {});
},
)
: null,
onTap: () {
if (isCoolingDown) {
// This will now execute correctly on tap
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Beneficiary will be enabled after the cooldown period.'),
behavior: SnackBarBehavior.floating,
),
);
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FundTransferAmountScreen(
debitAccountNo: widget.creditAccountNo,
creditBeneficiary: beneficiary,
remitterName: widget.remitterName,
isOwnBank: widget.isOwnBank,
),
),
);
}
},
),
),
);
},
@@ -162,7 +191,27 @@ class _FundTransferBeneficiaryScreenState
),
body: Stack(
children: [
_isLoading ? _buildShimmerList() : _buildBeneficiaryList(),
Column(
children: [
Padding(
padding: const EdgeInsets.all(12.0),
child: TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: "Search by name or account number",
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
Expanded(
child:
_isLoading ? _buildShimmerList() : _buildBeneficiaryList(),
),
],
),
IgnorePointer(
child: Center(
child: Opacity(

View File

@@ -34,66 +34,73 @@ class FundTransferScreen extends StatelessWidget {
builder: (context, state) {
return Stack(
children: [
ListView(
children: [
FundTransferManagementTile(
icon: Symbols.person,
// Restore localization for the label
label: "Self Pay",
onTap: () {
// The accounts list is passed directly from the constructor
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FundTransferSelfAccountsScreen(
debitAccountNo: creditAccountNo,
remitterName: remitterName,
accounts: accounts,
),
),
);
},
// Disable the tile if the state is not Authenticated
disable: state is! Authenticated,
),
const Divider(height: 1),
FundTransferManagementTile(
icon: Symbols.input_circle,
// Restore localization for the label
label: AppLocalizations.of(context).ownBank,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FundTransferBeneficiaryScreen(
creditAccountNo: creditAccountNo,
remitterName: remitterName,
isOwnBank: true,
),
),
);
},
),
const Divider(height: 1),
FundTransferManagementTile(
icon: Symbols.output_circle,
// Restore localization for the label
label: AppLocalizations.of(context).outsideBank,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FundTransferBeneficiaryScreen(
creditAccountNo: creditAccountNo,
remitterName: remitterName,
isOwnBank: false,
),
),
);
},
),
const Divider(height: 1),
],
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: FundTransferManagementTile(
icon: Symbols.person,
label: "Self Pay",
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
FundTransferSelfAccountsScreen(
debitAccountNo: creditAccountNo,
remitterName: remitterName,
accounts: accounts,
),
),
);
},
disable: state is! Authenticated,
),
),
const SizedBox(height: 16),
Expanded(
child: FundTransferManagementTile(
icon: Symbols.input_circle,
label: AppLocalizations.of(context).ownBank,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
FundTransferBeneficiaryScreen(
creditAccountNo: creditAccountNo,
remitterName: remitterName,
isOwnBank: true,
),
),
);
},
),
),
const SizedBox(height: 16),
Expanded(
child: FundTransferManagementTile(
icon: Symbols.output_circle,
label: AppLocalizations.of(context).outsideBank,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
FundTransferBeneficiaryScreen(
creditAccountNo: creditAccountNo,
remitterName: remitterName,
isOwnBank: false,
),
),
);
},
),
),
],
),
),
IgnorePointer(
child: Center(
@@ -133,12 +140,45 @@ class FundTransferManagementTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListTile(
leading: Icon(icon),
title: Text(label),
trailing: const Icon(Symbols.arrow_right, size: 20),
onTap: onTap,
enabled: !disable,
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: 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,
),
),
],
),
),
),
),
);
}
}

View File

@@ -55,39 +55,44 @@ class FundTransferSelfAccountsScreen extends StatelessWidget {
itemCount: filteredAccounts.length,
itemBuilder: (context, index) {
final account = filteredAccounts[index];
return ListTile(
leading: CircleAvatar(
radius: 24,
backgroundColor: Colors.transparent,
child: getBankLogo(
'Kangra Central Co-operative Bank', context),
),
title: Text(account.name ?? 'N/A'),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(account.accountNo ?? 'N/A'),
Text(
_getFullAccountType(account.accountType),
style: TextStyle(
fontSize: 12, color: Colors.grey[600]),
),
],
),
onTap: () {
// Navigate to the amount screen, passing the selected User object directly.
// No Beneficiary object is created.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FundTransferSelfAmountScreen(
debitAccountNo: debitAccountNo,
creditAccount: account, // Pass the User object
remitterName: remitterName,
return Card(
margin: const EdgeInsets.symmetric(
horizontal: 12, vertical: 6),
child: ListTile(
leading: CircleAvatar(
radius: 24,
backgroundColor: Colors.transparent,
child: getBankLogo(
'Kangra Central Co-operative Bank', context),
),
title: Text(account.name ?? 'N/A'),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(account.accountNo ?? 'N/A'),
Text(
_getFullAccountType(account.accountType),
style: TextStyle(
fontSize: 12, color: Colors.grey[600]),
),
),
);
},
],
),
onTap: () {
// Navigate to the amount screen, passing the selected User object directly.
// No Beneficiary object is created.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
FundTransferSelfAmountScreen(
debitAccountNo: debitAccountNo,
creditAccount: account, // Pass the User object
remitterName: remitterName,
),
),
);
},
),
);
},
),

View File

@@ -15,7 +15,13 @@ import 'package:kmobile/features/profile/preferences/preference_screen.dart';
class ProfileScreen extends StatefulWidget {
final String mobileNumber;
const ProfileScreen({super.key, required this.mobileNumber});
final String customerNo;
final String customerName;
const ProfileScreen(
{super.key,
required this.mobileNumber,
required this.customerNo,
required this.customerName});
@override
State<ProfileScreen> createState() => _ProfileScreenState();
@@ -160,31 +166,107 @@ class _ProfileScreenState extends State<ProfileScreen> {
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: Text(loc.profile), // Localized "Profile"
title: Text(loc.profile),
elevation: 0,
),
body: Stack(
children: [
ListView(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
children: [
ListTile(
leading: const Icon(Icons.settings),
title: Text(loc.preferences),
trailing: const Icon(Icons.chevron_right),
// ===== Profile Header =====
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
// Avatar
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,
),
),
),
const SizedBox(width: 12),
// Name + mobile
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
// If you want to show the user's name instead, replace below.
widget.customerName,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 4),
Text(
widget.customerNo,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface
.withOpacity(0.7),
),
),
],
),
),
// 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,
),
),
],
),
),
),
const SizedBox(height: 16),
// ===== Section: Settings =====
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
"Settings",
style: theme.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
letterSpacing: 0.2,
),
),
),
const SizedBox(height: 8),
_SectionTile(
leadingIcon: Icons.settings,
title: loc.preferences,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const PreferenceScreen()),
builder: (context) => const PreferenceScreen(),
),
);
},
),
ListTile(
leading: const Icon(Icons.security),
title: Text(loc.securitySettings),
trailing: const Icon(Icons.chevron_right),
_SectionTile(
leadingIcon: Icons.security,
title: loc.securitySettings,
onTap: () {
Navigator.push(
context,
@@ -196,66 +278,113 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
},
),
ListTile(
leading: const Icon(Icons.currency_rupee),
title: Text(AppLocalizations.of(context).dailylimit),
_SectionTile(
leadingIcon: Icons.currency_rupee,
title: loc.dailylimit,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DailyLimitScreen()),
builder: (context) => const DailyLimitScreen(),
),
);
},
),
SwitchListTile(
title:
Text(AppLocalizations.of(context).enableFingerprintLogin),
value: _isBiometricEnabled,
onChanged: (bool value) {
// The state is now managed within _handleBiometricToggle
_handleBiometricToggle(value);
},
secondary: const Icon(Icons.fingerprint),
),
ListTile(
leading: const Icon(Icons.smartphone),
title: const Text("App Version"),
trailing: FutureBuilder<String>(
future: _getAppVersion(),
builder:
(BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
// Show a loading indicator while waiting for the future to complete
return const CircularProgressIndicator();
} else if (snapshot.hasError) {
return const Text("Error");
} else {
// Display the version number once the future is complete
return Text(
snapshot.data ?? "N/A",
selectionColor: const Color(0xFFFFFFFF),
);
}
Card(
child: SwitchListTile(
title: Text(loc.enableFingerprintLogin),
value: _isBiometricEnabled,
onChanged: (bool value) {
_handleBiometricToggle(value);
},
secondary: const Icon(Icons.fingerprint),
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
),
),
ListTile(
leading: const Icon(Icons.exit_to_app),
title: Text(AppLocalizations.of(context).logout),
const SizedBox(height: 16),
const Divider(height: 24),
// ===== Section: Security & App =====
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
loc.appVersion,
style: theme.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
letterSpacing: 0.2,
),
),
),
const SizedBox(height: 8),
// Fingerprint toggle inside a styled container
Card(
child: ListTile(
leading: const Icon(Icons.smartphone),
title: Text(loc.appVersion),
trailing: FutureBuilder<String>(
future: _getAppVersion(),
builder:
(BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2),
);
} else if (snapshot.hasError) {
return Text(loc.error);
} else {
return Text(
snapshot.data ?? "N/A",
);
}
},
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
),
const SizedBox(height: 16),
const Divider(height: 24),
// ===== Section: Actions =====
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
"Exit",
style: theme.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
letterSpacing: 0.2,
),
),
),
const SizedBox(height: 8),
_SectionTile(
leadingIcon: Icons.exit_to_app,
title: loc.logout,
trailChevron: false, // action tile, no chevron
onTap: () async {
final shouldExit = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text(AppLocalizations.of(context).logout),
content: Text(AppLocalizations.of(context).logoutCheck),
title: Text(loc.logout),
content: Text(loc.logoutCheck),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(AppLocalizations.of(context).no),
child: Text(loc.no),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text(AppLocalizations.of(context).yes),
child: Text(loc.yes),
),
],
),
@@ -269,9 +398,10 @@ class _ProfileScreenState extends State<ProfileScreen> {
}
},
),
ListTile(
leading: const Icon(Icons.logout),
title: Text(AppLocalizations.of(context).deregister),
_SectionTile(
leadingIcon: Icons.logout,
title: loc.deregister,
trailChevron: false,
onTap: () async {
final shouldLogout = await showDialog<bool>(
context: context,
@@ -283,17 +413,24 @@ class _ProfileScreenState extends State<ProfileScreen> {
}
},
),
const SizedBox(height: 24),
],
),
// ===== Watermark (kept subtle, no theme change) =====
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
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,
),
),
),
),
@@ -304,3 +441,38 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
}
}
class _SectionTile extends StatelessWidget {
const _SectionTile({
required this.leadingIcon,
required this.title,
this.onTap,
this.trailChevron = true,
});
final IconData leadingIcon;
final String title;
final VoidCallback? onTap;
final bool trailChevron;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Card(
margin: const EdgeInsets.only(bottom: 10),
child: ListTile(
leading: Icon(leadingIcon, color: theme.colorScheme.onSurface),
title: Text(title, style: theme.textTheme.bodyLarge),
trailing: trailChevron
? Icon(Icons.chevron_right, color: theme.colorScheme.onSurface)
: null,
onTap: onTap,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
);
}
}

View File

@@ -40,7 +40,7 @@ class SecuritySettingsScreen extends StatelessWidget {
);
},
),
const Divider(height: 1),
Divider(height: 1, color: Theme.of(context).dividerColor),
ListTile(
leading: const Icon(Icons.pin),
title: Text(loc.changeMpin),
@@ -57,13 +57,14 @@ class SecuritySettingsScreen extends StatelessWidget {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(loc.mpinChangedSuccessfully),
backgroundColor: Colors.green,
backgroundColor:
Theme.of(context).colorScheme.secondary,
),
);
}
},
),
const Divider(height: 1),
Divider(height: 1, color: Theme.of(context).dividerColor),
ListTile(
leading: const Icon(Icons.password),
title: const Text('Change TPIN'),

View File

@@ -23,39 +23,46 @@ class _QuickPayScreen extends State<QuickPayScreen> {
),
body: Stack(
children: [
ListView(
children: [
QuickPayManagementTile(
icon: Symbols.input_circle,
label: AppLocalizations.of(context).ownBank,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => QuickPayWithinBankScreen(
debitAccount: widget.debitAccount,
),
),
);
},
),
const Divider(height: 1),
QuickPayManagementTile(
icon: Symbols.output_circle,
label: AppLocalizations.of(context).outsideBank,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => QuickPayOutsideBankScreen(
debitAccount: widget.debitAccount,
),
),
);
},
),
const Divider(height: 1),
],
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: QuickPayManagementTile(
icon: Symbols.input_circle,
label: AppLocalizations.of(context).ownBank,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => QuickPayWithinBankScreen(
debitAccount: widget.debitAccount,
),
),
);
},
),
),
const SizedBox(height: 16),
Expanded(
child: QuickPayManagementTile(
icon: Symbols.output_circle,
label: AppLocalizations.of(context).outsideBank,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => QuickPayOutsideBankScreen(
debitAccount: widget.debitAccount,
),
),
);
},
),
),
],
),
),
IgnorePointer(
child: Center(
@@ -93,12 +100,43 @@ class QuickPayManagementTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListTile(
leading: Icon(icon),
title: Text(label),
trailing: const Icon(Symbols.arrow_right, size: 20),
onTap: onTap,
enabled: !disable,
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: 36.0, horizontal: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
size: 64, // Make icon larger for two cards
color:
disable ? theme.disabledColor : theme.colorScheme.primary,
),
const SizedBox(height: 16),
Text(
label,
textAlign: TextAlign.center,
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
color: disable
? theme.disabledColor
: theme.colorScheme.onSurface,
),
),
],
),
),
),
);
}
}

View File

@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import '../../../l10n/app_localizations.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
@@ -15,10 +14,12 @@ class ATMLocatorScreen extends StatefulWidget {
class _ATMLocatorScreenState extends State<ATMLocatorScreen> {
final TextEditingController _searchController = TextEditingController();
var service = getIt<BranchService>(); // Added: Instance of BranchService for API calls
var service =
getIt<BranchService>(); // Added: Instance of BranchService for API calls
bool _isLoading = true; // State variable to manage loading status
List<Atm> _allAtms = []; // Changed: List to hold all fetched Atm objects
List<Atm> _filteredAtms = []; // Changed: List to hold filtered Atm objects for display
List<Atm> _filteredAtms =
[]; // Changed: List to hold filtered Atm objects for display
@override
void initState() {
@@ -28,7 +29,8 @@ class _ATMLocatorScreenState extends State<ATMLocatorScreen> {
/// Fetches the list of ATMs from the API using BranchService.
Future<void> _loadAtms() async {
final data = await service.fetchAtmList(); // Call the new fetchAtmList method
final data =
await service.fetchAtmList(); // Call the new fetchAtmList method
setState(() {
_allAtms = data; // Update the list of all ATMs
_filteredAtms = data; // Initialize filtered list with all ATMs
@@ -37,14 +39,18 @@ class _ATMLocatorScreenState extends State<ATMLocatorScreen> {
}
/// Filters the list of ATMs based on the search query.
void _filterAtms(String query) { // Changed: Renamed from _filterLocations
void _filterAtms(String query) {
// Changed: Renamed from _filterLocations
setState(() {
if (query.isEmpty) {
_filteredAtms = _allAtms; // If query is empty, show all ATMs
} else {
_filteredAtms = _allAtms.where((atm) { // Changed: Filter based on Atm object
_filteredAtms = _allAtms.where((atm) {
// Changed: Filter based on Atm object
final lowerQuery = query.toLowerCase();
return atm.name.toLowerCase().contains(lowerQuery); // Filter by atm.name
return atm.name
.toLowerCase()
.contains(lowerQuery); // Filter by atm.name
}).toList();
}
});
@@ -64,7 +70,8 @@ class _ATMLocatorScreenState extends State<ATMLocatorScreen> {
padding: const EdgeInsets.all(12.0),
child: TextField(
controller: _searchController,
onChanged: _filterAtms, // Updated: Call _filterAtms on text change
onChanged:
_filterAtms, // Updated: Call _filterAtms on text change
decoration: InputDecoration(
hintText: "Name/Address", // Hint text for the search bar
prefixIcon: const Icon(Icons.search), // Search icon
@@ -81,12 +88,16 @@ class _ATMLocatorScreenState extends State<ATMLocatorScreen> {
? _buildShimmerList() // Display shimmer while loading
: _filteredAtms.isEmpty
? const Center(
child: Text("No matching ATMs found")) // Message if no ATMs found
child: Text(
"No matching ATMs found")) // Message if no ATMs found
: ListView.builder(
itemCount: _filteredAtms.length, // Number of items in the filtered list
itemCount: _filteredAtms
.length, // Number of items in the filtered list
itemBuilder: (context, index) {
final atm = _filteredAtms[index]; // Get the current Atm object
return _buildAtmItem(atm); // Build the ATM list item
final atm = _filteredAtms[
index]; // Get the current Atm object
return _buildAtmItem(
atm); // Build the ATM list item
},
),
),
@@ -111,9 +122,9 @@ class _ATMLocatorScreenState extends State<ATMLocatorScreen> {
);
}
/// Helper widget to build a single ATM list item.
Widget _buildAtmItem(Atm atm) { // Changed: Takes an Atm object
Widget _buildAtmItem(Atm atm) {
// Changed: Takes an Atm object
return Card(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: ListTile(

View File

@@ -1,107 +1,109 @@
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/branch_service.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/branch_service.dart';
import 'package:url_launcher/url_launcher.dart';
class BranchDetailsScreen extends StatelessWidget {
final Branch branch;
class BranchDetailsScreen extends StatelessWidget {
final Branch branch;
const BranchDetailsScreen({super.key, required this.branch});
const BranchDetailsScreen({super.key, required this.branch});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(branch.branch_name),
),
body: Stack(
children: [
SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildDetailRow("Branch Name", branch.branch_name),
_buildDetailRow("Branch Code", branch.branch_code),
_buildDetailRow("Zone", branch.zone),
_buildDetailRow("Tehsil", branch.tehsil),
_buildDetailRow("Block", branch.block),
_buildDetailRow("District", branch.distt_name),
_buildDetailRow("Pincode", branch.pincode),
// _buildDetailRow("Post Office", branch.post_office),
// _buildDetailRow("Date of Opening", branch.date_of_opening),
// _buildDetailRow("Branch Type", branch.type_of_branch),
_buildDetailRow("Telephone No.", branch.telephone_no),
// _buildDetailRow("RTGS Account No.", branch.rtgs_acct_no),
// _buildDetailRow("RBI Code 1", branch.rbi_code_1),
// _buildDetailRow("RBI Code 2", branch.rbi_code_2),
// _buildDetailRow("Latitude", branch.br_lattitude),
// _buildDetailRow("Longitude", branch.br_longitude),
],
),
),
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
),
),
),
),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(branch.branch_name),
),
body: Stack(
children: [
SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildDetailRow(context, "Branch Name", branch.branch_name),
_buildDetailRow(context, "Branch Code", branch.branch_code),
_buildDetailRow(context, "Zone", branch.zone),
_buildDetailRow(context, "Tehsil", branch.tehsil),
_buildDetailRow(context, "Block", branch.block),
_buildDetailRow(context, "District", branch.distt_name),
_buildDetailRow(context, "Pincode", branch.pincode),
// _buildDetailRow(context, "Post Office", branch.post_office),
// _buildDetailRow(context, "Date of Opening", branch.date_of_opening),
// _buildDetailRow(context, "Branch Type", branch.type_of_branch),
_buildDetailRow(context, "Telephone No.", branch.telephone_no),
// _buildDetailRow("RTGS Account No.", branch.rtgs_acct_no),
// _buildDetailRow("RBI Code 1", branch.rbi_code_1),
// _buildDetailRow("RBI Code 2", branch.rbi_code_2),
// _buildDetailRow("Latitude", branch.br_lattitude),
// _buildDetailRow("Longitude", branch.br_longitude),
],
),
),
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 _buildDetailRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
const SizedBox(height: 4),
label == "Telephone No."
? InkWell(
onTap: () => _launchUrl('tel:$value'),
child: Text(
value,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.blue, // Indicate it's clickable
decoration: TextDecoration.underline,
),
),
)
: Text(
value,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
const Divider(height: 16),
],
),
);
}
Widget _buildDetailRow(BuildContext context, String label, String value) {
final theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 14,
color: theme.textTheme.bodySmall?.color,
),
),
const SizedBox(height: 4),
label == "Telephone No."
? InkWell(
onTap: () => _launchUrl('tel:$value'),
child: Text(
value,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color:
theme.colorScheme.primary, // Indicate it's clickable
decoration: TextDecoration.underline,
),
),
)
: Text(
value,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
Divider(height: 16, color: theme.dividerColor),
],
),
);
}
Future<void> _launchUrl(String urlString) async {
final Uri url = Uri.parse(urlString);
if (!await launchUrl(url)) {
throw 'Could not launch $urlString';
}
}
}
Future<void> _launchUrl(String urlString) async {
final Uri url = Uri.parse(urlString);
if (!await launchUrl(url)) {
throw 'Could not launch $urlString';
}
}
}

View File

@@ -1,13 +1,11 @@
// ignore_for_file: unused_element
import 'package:flutter/material.dart';
import '../../../l10n/app_localizations.dart';
import 'package:kmobile/api/services/branch_service.dart';
import 'package:kmobile/di/injection.dart';
import 'package:shimmer/shimmer.dart';
import 'package:kmobile/features/service/screens/branch_details_screen.dart';
class BranchLocatorScreen extends StatefulWidget {
const BranchLocatorScreen({super.key});
@@ -15,48 +13,52 @@ class BranchLocatorScreen extends StatefulWidget {
State<BranchLocatorScreen> createState() => _BranchLocatorScreenState();
}
class _BranchLocatorScreenState extends State<BranchLocatorScreen> {
final TextEditingController _searchController = TextEditingController();
var service = getIt<BranchService>();
bool _isLoading = true;
List<Branch> _allBranches = [];
List<Branch> _filteredBranches = [];
class _BranchLocatorScreenState extends State<BranchLocatorScreen> {
final TextEditingController _searchController = TextEditingController();
var service = getIt<BranchService>();
bool _isLoading = true;
List<Branch> _allBranches = [];
List<Branch> _filteredBranches = [];
@override
@override
void initState() {
super.initState();
// _fetchAndSetLocations();
_loadBranches();
}
Future<void> _loadBranches() async {
final data = await service.fetchBranchList();
setState(() {
_allBranches = data;
_filteredBranches = data;
_isLoading = false;
});
}
Future<void> _loadBranches() async {
final data = await service.fetchBranchList();
setState(() {
_allBranches = data;
_filteredBranches = data;
_isLoading = false;
});
}
void _filterBranches(String query) {
setState(() {
if (query.isEmpty) {
_filteredBranches = _allBranches;
} else {
_filteredBranches = _allBranches.where((branch) {
final lowerQuery = query.toLowerCase();
return branch.branch_name.toLowerCase().contains(lowerQuery);
}).toList();
}
});
}
setState(() {
if (query.isEmpty) {
_filteredBranches = _allBranches;
} else {
_filteredBranches = _allBranches.where((branch) {
final lowerQuery = query.toLowerCase();
return branch.branch_name.toLowerCase().contains(lowerQuery);
}).toList();
}
});
}
@override
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Branch Locator"),
title: Text(
"Branch Locator",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
),
),
),
body: Stack(
children: [
@@ -83,11 +85,13 @@ class BranchLocatorScreen extends StatefulWidget {
? _buildShimmerList() // Changed to shimmer
: _filteredBranches.isEmpty
? const Center(
child: Text("No matching branches found")) // Updated tex
child: Text(
"No matching branches found")) // Updated tex
: ListView.builder(
itemCount: _filteredBranches.length,
itemBuilder: (context, index) {
final branch = _filteredBranches[index]; // Changed to
final branch =
_filteredBranches[index]; // Changed to
return _buildBranchItem(branch); // Updated
},
),
@@ -127,7 +131,7 @@ class BranchLocatorScreen extends StatefulWidget {
}
// Helper widget to build a single branch item
Widget _buildBranchItem(Branch branch) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
@@ -150,7 +154,6 @@ class BranchLocatorScreen extends StatefulWidget {
);
}
// Shimmer loading list
Widget _buildShimmerList() {
return ListView.builder(

View File

@@ -110,4 +110,4 @@ class _FaqsScreenState extends State<FaqsScreen> {
),
);
}
}
}

View File

@@ -1,113 +1,113 @@
//
//
import 'package:flutter/material.dart';
import 'package:kmobile/l10n/app_localizations.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter/material.dart';
import 'package:kmobile/l10n/app_localizations.dart';
import 'package:url_launcher/url_launcher.dart';
// Data model for a single Quick Link item
class QuickLink {
final String title;
final String url;
// The icon is no longer used in the UI but is kept in the model for potential future use.
final IconData icon;
// Data model for a single Quick Link item
class QuickLink {
final String title;
final String url;
// The icon is no longer used in the UI but is kept in the model for potential future use.
final IconData icon;
QuickLink({required this.title, required this.url, required this.icon});
}
QuickLink({required this.title, required this.url, required this.icon});
}
class QuickLinksScreen extends StatefulWidget {
const QuickLinksScreen({super.key});
class QuickLinksScreen extends StatefulWidget {
const QuickLinksScreen({super.key});
@override
State<QuickLinksScreen> createState() => _QuickLinksScreenState();
}
@override
State<QuickLinksScreen> createState() => _QuickLinksScreenState();
}
class _QuickLinksScreenState extends State<QuickLinksScreen> {
// List of Quick Links
final List<QuickLink> _quickLinks = [
QuickLink(
title: "National Bank of Agriculture & Rural Development",
url: "http://www.nabard.org/",
icon: Icons.account_balance),
QuickLink(
title: "Reserve Bank of India",
url: "http://www.rbi.org.in/home.aspx",
icon: Icons.account_balance_wallet),
QuickLink(
title: "Indian Institute of Banking & Finance",
url: "http://www.iibf.org.in/",
icon: Icons.school),
QuickLink(
title: "Indian Bank Association",
url: "http://www.iba.org.in/",
icon: Icons.group_work),
QuickLink(
title: "Ministry of Finance",
url: "http://www.finmin.nic.in/",
icon: Icons.business),
QuickLink(
title: "Securities Exchange Board of India",
url: "http://www.sebi.gov.in/",
icon: Icons.show_chart),
QuickLink(
title: "Insurance Regulatory & Development Authority",
url: "https://www.irdai.gov.in/",
icon: Icons.shield_outlined),
];
class _QuickLinksScreenState extends State<QuickLinksScreen> {
// List of Quick Links
final List<QuickLink> _quickLinks = [
QuickLink(
title: "National Bank of Agriculture & Rural Development",
url: "http://www.nabard.org/",
icon: Icons.account_balance),
QuickLink(
title: "Reserve Bank of India",
url: "http://www.rbi.org.in/home.aspx",
icon: Icons.account_balance_wallet),
QuickLink(
title: "Indian Institute of Banking & Finance",
url: "http://www.iibf.org.in/",
icon: Icons.school),
QuickLink(
title: "Indian Bank Association",
url: "http://www.iba.org.in/",
icon: Icons.group_work),
QuickLink(
title: "Ministry of Finance",
url: "http://www.finmin.nic.in/",
icon: Icons.business),
QuickLink(
title: "Securities Exchange Board of India",
url: "http://www.sebi.gov.in/",
icon: Icons.show_chart),
QuickLink(
title: "Insurance Regulatory & Development Authority",
url: "https://www.irdai.gov.in/",
icon: Icons.shield_outlined),
];
// Function to launch a URL
Future<void> _launchURL(String url, BuildContext context) async {
final Uri uri = Uri.parse(url);
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Could not launch $url')),
);
}
}
// Function to launch a URL
Future<void> _launchURL(String url, BuildContext context) async {
final Uri uri = Uri.parse(url);
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Could not launch $url')),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context).quickLinks),
),
body: Stack(
children: [
// Background logo
IgnorePointer(
child: Center(
child: Opacity(
opacity: 0.07,
child: ClipOval(
child: Image.asset(
'assets/images/logo.png',
width: 200,
height: 200,
),
),
),
),
),
// UPDATED: List of Quick Links
ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 8.0),
itemCount: _quickLinks.length,
itemBuilder: (context, index) {
final link = _quickLinks[index];
return Card(
margin:
const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
child: ListTile(
title: Text(link.title),
trailing: const Icon(Icons.open_in_new, size: 20),
onTap: () => _launchURL(link.url, context),
),
);
},
),
],
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context).quickLinks),
),
body: Stack(
children: [
// Background logo
IgnorePointer(
child: Center(
child: Opacity(
opacity: 0.07,
child: ClipOval(
child: Image.asset(
'assets/images/logo.png',
width: 200,
height: 200,
),
),
),
),
),
// UPDATED: List of Quick Links
ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 8.0),
itemCount: _quickLinks.length,
itemBuilder: (context, index) {
final link = _quickLinks[index];
return Card(
margin:
const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
child: ListTile(
title: Text(link.title),
trailing: const Icon(Icons.open_in_new, size: 20),
onTap: () => _launchURL(link.url, context),
),
);
},
),
],
),
);
}
}

View File

@@ -1,6 +1,4 @@
import 'package:kmobile/features/service/screens/atm_locator_screen.dart';
import 'package:kmobile/features/service/screens/branch_locator_screen.dart';
import '../../../l10n/app_localizations.dart';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
@@ -27,58 +25,55 @@ class _ServiceScreen extends State<ServiceScreen> {
),
body: Stack(
children: [
ListView(
children: [
// ServiceManagementTile(
// icon: Symbols.add,
// label: AppLocalizations.of(context).accountOpeningDeposit,
// onTap: () {},
// disabled: true,
// ),
// const Divider(height: 1),
// ServiceManagementTile(
// icon: Symbols.add,
// label: AppLocalizations.of(context).accountOpeningLoan,
// onTap: () {},
// disabled: true,
// ),
// const Divider(height: 1),
ServiceManagementTile(
icon: Symbols.captive_portal,
label: AppLocalizations.of(context).quickLinks,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const QuickLinksScreen()),
);
},
disabled: false,
),
const Divider(height: 1),
ServiceManagementTile(
icon: Symbols.question_mark,
label: AppLocalizations.of(context).faq,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const FaqsScreen()),
);
},
disabled: false,
),
const Divider(height: 1),
ServiceManagementTile(
icon: Symbols.location_pin,
label: "ATM Locator",
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ATMLocatorScreen()));
},
disabled: false,
),
const Divider(height: 1),
],
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: ServiceManagementTile(
icon: Symbols.captive_portal,
label: AppLocalizations.of(context).quickLinks,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const QuickLinksScreen()),
);
},
disabled: false,
),
),
const SizedBox(height: 16),
Expanded(
child: ServiceManagementTile(
icon: Symbols.question_mark,
label: AppLocalizations.of(context).faq,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const FaqsScreen()),
);
},
disabled: false,
),
),
const SizedBox(height: 16),
Expanded(
child: ServiceManagementTile(
icon: Symbols.location_pin,
label: "ATM Locator",
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ATMLocatorScreen()));
},
disabled: false,
),
),
// No Spacer() needed here as Expanded children will fill space
],
),
),
IgnorePointer(
child: Center(
@@ -117,23 +112,42 @@ class ServiceManagementTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return ListTile(
leading: Icon(
icon,
color: disabled ? theme.disabledColor : null,
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
title: Text(
label,
style: TextStyle(
color: disabled ? theme.disabledColor : null,
elevation: 4, // Add some elevation for better visual separation
child: InkWell(
onTap:
disabled ? null : onTap, // Disable InkWell if the tile is disabled
borderRadius: BorderRadius.circular(12.0),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
size: 48, // Make icon larger
color:
disabled ? theme.disabledColor : theme.colorScheme.primary,
),
const SizedBox(height: 12),
Text(
label,
textAlign: TextAlign.center,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: disabled
? theme.disabledColor
: theme.colorScheme.onSurface,
),
),
],
),
),
),
trailing: Icon(
Symbols.arrow_right,
size: 20,
color: disabled ? theme.disabledColor : null,
),
onTap: disabled ? null : onTap,
);
}
}

View File

@@ -404,5 +404,7 @@
"postOffice": "Post Office",
"rbiCode1": "RBI Code 1",
"rbiCode2": "RBI Code 2",
"latitude": "Latitude"
"latitude": "Latitude",
"address": "Customer Address",
"transactions": "Transactions"
}

View File

@@ -405,5 +405,7 @@
"postOffice": "डाकघर",
"rbiCode1": "आरबीआई कोड 1",
"rbiCode2": "आरबीआई कोड 2",
"latitude": "अक्षांश"
"latitude": "अक्षांश",
"address": "ग्राहक का पता",
"transactions": "लेनदेन"
}

View File

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