Compare commits
7 Commits
fda5d075ff
...
adb9a5330b
| Author | SHA1 | Date | |
|---|---|---|---|
| adb9a5330b | |||
| 0075abc906 | |||
| 353ec63916 | |||
| 71b52cfb43 | |||
| c1df43e9b6 | |||
| f0d5233afc | |||
| 4fe6af4098 |
49
lib/app.dart
49
lib/app.dart
@@ -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
|
||||
@@ -133,7 +129,7 @@ class _KMobileState extends State<KMobile> with WidgetsBindingObserver {
|
||||
themeMode: context.watch<ThemeModeCubit>().state.mode,
|
||||
onGenerateRoute: AppRoutes.generateRoute,
|
||||
initialRoute: AppRoutes.splash,
|
||||
home: showSplash ? const SplashScreen() : const AuthGate(),
|
||||
home: const AuthGate(),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -205,7 +201,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 +210,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 +312,24 @@ 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) {
|
||||
final user = state.users.first;
|
||||
return AccountStatementScreen(
|
||||
accountNo: user.accountNo ?? '',
|
||||
balance: user.availableBalance ?? '0.00',
|
||||
accountType: user.accountType ?? '',
|
||||
);
|
||||
} else {
|
||||
return const Center(child: Text("No accounts found."));
|
||||
}
|
||||
}
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
},
|
||||
),
|
||||
const CardManagementScreen(),
|
||||
const ServiceScreen(),
|
||||
];
|
||||
|
||||
@@ -374,10 +387,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 +439,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 {
|
||||
|
||||
@@ -71,9 +71,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-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
|
||||
'https://kccbmbnk.net', //prod small
|
||||
connectTimeout: const Duration(seconds: 60),
|
||||
receiveTimeout: const Duration(seconds: 60),
|
||||
headers: {
|
||||
|
||||
@@ -97,12 +97,14 @@ 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,
|
||||
);
|
||||
@@ -236,8 +238,8 @@ 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 +247,8 @@ 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 +257,8 @@ 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 +288,7 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
||||
? Symbols.call_received
|
||||
: Symbols.call_made,
|
||||
color: tx.type == 'CR'
|
||||
? Colors.green
|
||||
? Theme.of(context).colorScheme.secondary
|
||||
: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
title: Text(
|
||||
@@ -330,7 +332,7 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return const Divider();
|
||||
return Divider(color: Theme.of(context).dividerColor);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
@@ -42,7 +42,9 @@ class TransactionDetailsScreen extends StatelessWidget {
|
||||
isCredit
|
||||
? Symbols.call_received
|
||||
: Symbols.call_made,
|
||||
color: isCredit ? Colors.green : Colors.red,
|
||||
color: isCredit
|
||||
? Theme.of(context).colorScheme.secondary
|
||||
: 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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
@@ -77,7 +77,7 @@ class _CardManagementScreen extends State<CardManagementScreen> {
|
||||
},
|
||||
disabled: true,
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||
],
|
||||
),
|
||||
IgnorePointer(
|
||||
|
||||
@@ -18,7 +18,7 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
|
||||
title: Text(
|
||||
AppLocalizations.of(context).chequeManagement,
|
||||
style:
|
||||
const TextStyle(color: Colors.black, fontWeight: FontWeight.w500),
|
||||
TextStyle(color: Theme.of(context).textTheme.titleLarge?.color, fontWeight: FontWeight.w500),
|
||||
),
|
||||
centerTitle: false,
|
||||
),
|
||||
@@ -32,7 +32,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 +44,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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -2,6 +2,7 @@ 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/models/user.dart';
|
||||
import 'package:kmobile/data/repositories/transaction_repository.dart';
|
||||
import 'package:kmobile/di/injection.dart';
|
||||
import 'package:kmobile/features/accounts/screens/account_info_screen.dart';
|
||||
@@ -10,6 +11,7 @@ import 'package:kmobile/features/accounts/screens/transaction_details_screen.dar
|
||||
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';
|
||||
@@ -34,13 +36,167 @@ class DashboardScreen extends StatefulWidget {
|
||||
class _DashboardScreenState extends State<DashboardScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
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;
|
||||
PageController? _pageController;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pageController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
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.9;
|
||||
return AnimatedScale(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
scale: scale,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 18,
|
||||
vertical: 10,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF01A04C),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
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, // Changed to bold
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (isSelected) // Only show refresh for selected card
|
||||
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 Spacer(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"₹ ",
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onPrimary,
|
||||
fontSize: 32, // Adjusted for space
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
if (isRefreshing || (isBalanceLoading && isSelected))
|
||||
Expanded(child: _buildBalanceShimmer())
|
||||
else
|
||||
Expanded(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
isCardVisible ? user.currentBalance ?? '0.00' : '*****',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onPrimary,
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
if (isSelected) // Only show toggle for selected card
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
if (isBalanceLoading) return;
|
||||
final accountNo = user.accountNo;
|
||||
if (accountNo == null) return;
|
||||
|
||||
final bool currentVisibility =
|
||||
_visibilityMap[accountNo] ?? false;
|
||||
|
||||
if (!currentVisibility) {
|
||||
// If it's currently hidden, we show it after a delay
|
||||
setState(() {
|
||||
isBalanceLoading = true;
|
||||
});
|
||||
await Future.delayed(
|
||||
const Duration(milliseconds: 500)); // Shorter delay
|
||||
setState(() {
|
||||
_visibilityMap[accountNo] = true;
|
||||
isBalanceLoading = false;
|
||||
});
|
||||
} else {
|
||||
// If it's currently visible, we hide it immediately
|
||||
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(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadTransactions(String accountNo) async {
|
||||
setState(() {
|
||||
@@ -103,7 +259,7 @@ class _DashboardScreenState extends State<DashboardScreen>
|
||||
baseColor: theme.colorScheme.primary,
|
||||
highlightColor: theme.colorScheme.onPrimary,
|
||||
child: Container(
|
||||
width: 200, height: 42, color: theme.scaffoldBackgroundColor),
|
||||
height: 36, color: theme.scaffoldBackgroundColor),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -285,10 +441,13 @@ class _DashboardScreenState extends State<DashboardScreen>
|
||||
_loadTransactions(currAccount.accountNo!);
|
||||
});
|
||||
}
|
||||
_pageController ??= PageController(
|
||||
initialPage: selectedAccountIndex,
|
||||
viewportFraction: 0.85,
|
||||
);
|
||||
final firstName = getProcessedFirstName(currAccount.name);
|
||||
|
||||
return SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
@@ -307,162 +466,34 @@ 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 {
|
||||
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,
|
||||
),
|
||||
),
|
||||
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),
|
||||
],
|
||||
// Account Info Cards
|
||||
SizedBox(
|
||||
height: 160, // Adjusted height
|
||||
child: PageView.builder(
|
||||
controller: _pageController,
|
||||
itemCount: users.length,
|
||||
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!,
|
||||
);
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
final user = users[index];
|
||||
final isSelected = index == selectedAccountIndex;
|
||||
return _buildAccountCard(user, isSelected);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
@@ -470,13 +501,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,
|
||||
@@ -579,69 +613,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(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -689,28 +676,40 @@ 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,44 @@ 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -308,6 +308,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context);
|
||||
final theme = Theme.of(context);
|
||||
final isDarkMode = theme.brightness == Brightness.dark;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
@@ -320,83 +322,75 @@ Widget build(BuildContext context) {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
children: [
|
||||
// ===== Profile Header =====
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Avatar
|
||||
Container(
|
||||
width: 56,
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.grey.shade200,
|
||||
image: const DecorationImage(
|
||||
image: AssetImage('assets/images/logo.png'),
|
||||
fit: BoxFit.cover,
|
||||
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: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
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: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey.shade600,
|
||||
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: Colors.blueGrey.shade700,
|
||||
// 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 =====
|
||||
const Text(
|
||||
"Settings",
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.2,
|
||||
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),
|
||||
@@ -439,18 +433,7 @@ Widget build(BuildContext context) {
|
||||
);
|
||||
},
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
Card(
|
||||
child: SwitchListTile(
|
||||
title: Text(loc.enableFingerprintLogin),
|
||||
value: _isBiometricEnabled,
|
||||
@@ -467,35 +450,27 @@ Widget build(BuildContext context) {
|
||||
const Divider(height: 24),
|
||||
|
||||
// ===== Section: Security & App =====
|
||||
Text(
|
||||
loc.appVersion,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.2,
|
||||
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
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
Card(
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.smartphone),
|
||||
title: Text(loc.appVersion),
|
||||
trailing: FutureBuilder<String>(
|
||||
future: _getAppVersion(),
|
||||
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
|
||||
builder:
|
||||
(BuildContext context, AsyncSnapshot<String> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const SizedBox(
|
||||
width: 18,
|
||||
@@ -507,7 +482,6 @@ Widget build(BuildContext context) {
|
||||
} else {
|
||||
return Text(
|
||||
snapshot.data ?? "N/A",
|
||||
selectionColor: const Color(0xFFFFFFFF),
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -524,12 +498,14 @@ Widget build(BuildContext context) {
|
||||
const Divider(height: 24),
|
||||
|
||||
// ===== Section: Actions =====
|
||||
const Text(
|
||||
"Exit",
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.2,
|
||||
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),
|
||||
@@ -608,6 +584,7 @@ Widget build(BuildContext context) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SectionTile extends StatelessWidget {
|
||||
const _SectionTile({
|
||||
required this.leadingIcon,
|
||||
@@ -623,23 +600,16 @@ class _SectionTile extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ListTile(
|
||||
leading: Icon(leadingIcon),
|
||||
title: Text(title),
|
||||
trailing: trailChevron ? const Icon(Icons.chevron_right) : null,
|
||||
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),
|
||||
|
||||
@@ -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,13 @@ 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'),
|
||||
|
||||
@@ -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,42 @@ 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,17 +20,17 @@
|
||||
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(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),
|
||||
@@ -58,46 +58,46 @@
|
||||
);
|
||||
}
|
||||
|
||||
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],
|
||||
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(
|
||||
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,
|
||||
color: Colors.blue, // Indicate it's clickable
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const Divider(height: 16),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Divider(height: 16, color: theme.dividerColor),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
Future<void> _launchUrl(String urlString) async {
|
||||
final Uri url = Uri.parse(urlString);
|
||||
if (!await launchUrl(url)) {
|
||||
|
||||
@@ -56,7 +56,12 @@ class BranchLocatorScreen extends StatefulWidget {
|
||||
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: [
|
||||
|
||||
@@ -27,58 +27,54 @@ 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 +113,38 @@ 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,5 +404,7 @@
|
||||
"postOffice": "Post Office",
|
||||
"rbiCode1": "RBI Code 1",
|
||||
"rbiCode2": "RBI Code 2",
|
||||
"latitude": "Latitude"
|
||||
"latitude": "Latitude",
|
||||
"address": "Customer Address",
|
||||
"transactions": "Transactions"
|
||||
}
|
||||
|
||||
@@ -405,5 +405,7 @@
|
||||
"postOffice": "डाकघर",
|
||||
"rbiCode1": "आरबीआई कोड 1",
|
||||
"rbiCode2": "आरबीआई कोड 2",
|
||||
"latitude": "अक्षांश"
|
||||
"latitude": "अक्षांश",
|
||||
"address": "ग्राहक का पता",
|
||||
"transactions": "लेनदेन"
|
||||
}
|
||||
|
||||
64
pubspec.lock
64
pubspec.lock
@@ -69,10 +69,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.4.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -93,18 +93,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.19.1"
|
||||
confetti:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -181,10 +181,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
version: "1.3.3"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -385,10 +385,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.0"
|
||||
version: "0.20.2"
|
||||
jailbreak_root_detection:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -417,26 +417,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.5"
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -497,10 +497,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.16+1"
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -521,10 +521,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
version: "1.16.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -561,10 +561,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.0"
|
||||
version: "1.9.1"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -817,7 +817,7 @@ packages:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
version: "0.0.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -838,18 +838,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -870,10 +870,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
version: "0.7.6"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -982,10 +982,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1043,5 +1043,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.5.0 <4.0.0"
|
||||
dart: ">=3.8.0-0 <4.0.0"
|
||||
flutter: ">=3.24.0"
|
||||
|
||||
Reference in New Issue
Block a user