Compare commits
8 Commits
testing-2
...
adb9a5330b
| Author | SHA1 | Date | |
|---|---|---|---|
| adb9a5330b | |||
| 0075abc906 | |||
| 353ec63916 | |||
| 71b52cfb43 | |||
| c1df43e9b6 | |||
| f0d5233afc | |||
| 4fe6af4098 | |||
| fda5d075ff |
49
lib/app.dart
49
lib/app.dart
@@ -13,7 +13,9 @@ import 'config/routes.dart';
|
|||||||
import 'di/injection.dart';
|
import 'di/injection.dart';
|
||||||
import 'features/auth/controllers/auth_cubit.dart';
|
import 'features/auth/controllers/auth_cubit.dart';
|
||||||
import 'features/card/screens/card_management_screen.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/auth/screens/login_screen.dart';
|
||||||
import 'features/service/screens/service_screen.dart';
|
import 'features/service/screens/service_screen.dart';
|
||||||
import 'features/dashboard/screens/dashboard_screen.dart';
|
import 'features/dashboard/screens/dashboard_screen.dart';
|
||||||
@@ -37,7 +39,6 @@ class KMobile extends StatefulWidget {
|
|||||||
|
|
||||||
class _KMobileState extends State<KMobile> with WidgetsBindingObserver {
|
class _KMobileState extends State<KMobile> with WidgetsBindingObserver {
|
||||||
Timer? _backgroundTimer;
|
Timer? _backgroundTimer;
|
||||||
bool showSplash = true;
|
|
||||||
Locale? _locale;
|
Locale? _locale;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -45,11 +46,6 @@ class _KMobileState extends State<KMobile> with WidgetsBindingObserver {
|
|||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
loadPreferences();
|
loadPreferences();
|
||||||
Future.delayed(const Duration(seconds: 3), () {
|
|
||||||
setState(() {
|
|
||||||
showSplash = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -133,7 +129,7 @@ class _KMobileState extends State<KMobile> with WidgetsBindingObserver {
|
|||||||
themeMode: context.watch<ThemeModeCubit>().state.mode,
|
themeMode: context.watch<ThemeModeCubit>().state.mode,
|
||||||
onGenerateRoute: AppRoutes.generateRoute,
|
onGenerateRoute: AppRoutes.generateRoute,
|
||||||
initialRoute: AppRoutes.splash,
|
initialRoute: AppRoutes.splash,
|
||||||
home: showSplash ? const SplashScreen() : const AuthGate(),
|
home: const AuthGate(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -205,7 +201,7 @@ class _AuthGateState extends State<AuthGate> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_checking) {
|
if (_checking) {
|
||||||
return const SplashScreen();
|
return const LoginScreen();
|
||||||
}
|
}
|
||||||
if (_isLoggedIn) {
|
if (_isLoggedIn) {
|
||||||
if (_hasMPin) {
|
if (_hasMPin) {
|
||||||
@@ -214,7 +210,7 @@ class _AuthGateState extends State<AuthGate> {
|
|||||||
future: _tryBiometric(),
|
future: _tryBiometric(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
return const SplashScreen();
|
return const LoginScreen();
|
||||||
}
|
}
|
||||||
if (snapshot.data == true) {
|
if (snapshot.data == true) {
|
||||||
return const NavigationScaffold();
|
return const NavigationScaffold();
|
||||||
@@ -316,7 +312,24 @@ class _NavigationScaffoldState extends State<NavigationScaffold> {
|
|||||||
int _selectedIndex = 0;
|
int _selectedIndex = 0;
|
||||||
final List<Widget> _pages = [
|
final List<Widget> _pages = [
|
||||||
const DashboardScreen(),
|
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(),
|
const ServiceScreen(),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -374,10 +387,14 @@ class _NavigationScaffoldState extends State<NavigationScaffold> {
|
|||||||
icon: const Icon(Icons.home_filled),
|
icon: const Icon(Icons.home_filled),
|
||||||
label: AppLocalizations.of(context).home,
|
label: AppLocalizations.of(context).home,
|
||||||
),
|
),
|
||||||
// BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
// icon: const Icon(Icons.credit_card),
|
icon: const Icon(Icons.swap_vert_sharp),
|
||||||
// label: AppLocalizations.of(context).card,
|
label: AppLocalizations.of(context).transactions,
|
||||||
// ),
|
),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: const Icon(Icons.credit_card),
|
||||||
|
label: AppLocalizations.of(context).card,
|
||||||
|
),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: const Icon(Icons.miscellaneous_services),
|
icon: const Icon(Icons.miscellaneous_services),
|
||||||
label: AppLocalizations.of(context).services,
|
label: AppLocalizations.of(context).services,
|
||||||
@@ -422,7 +439,7 @@ class BiometricPromptScreen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Future.microtask(() => _showDialog(context));
|
Future.microtask(() => _showDialog(context));
|
||||||
return const SplashScreen();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showDialog(BuildContext context) async {
|
Future<void> _showDialog(BuildContext context) async {
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ Dio _createDioClient() {
|
|||||||
final dio = Dio(
|
final dio = Dio(
|
||||||
BaseOptions(
|
BaseOptions(
|
||||||
baseUrl:
|
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
|
//'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),
|
connectTimeout: const Duration(seconds: 60),
|
||||||
|
|||||||
@@ -97,12 +97,14 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final maxToDate = fromDate!.add(const Duration(days: 31)).isBefore(now)
|
final maxToDate = fromDate!.add(const Duration(days: 183)).isBefore(now)
|
||||||
? fromDate!.add(const Duration(days: 31))
|
? fromDate!.add(const Duration(days: 183))
|
||||||
: now;
|
: now;
|
||||||
|
final initialToDate = toDate ?? now;
|
||||||
|
final clampedInitialToDate = initialToDate.isBefore(fromDate!) ? fromDate! : initialToDate;
|
||||||
final picked = await showDatePicker(
|
final picked = await showDatePicker(
|
||||||
context: context,
|
context: context,
|
||||||
initialDate: toDate ?? fromDate!,
|
initialDate: clampedInitialToDate,
|
||||||
firstDate: fromDate!,
|
firstDate: fromDate!,
|
||||||
lastDate: maxToDate,
|
lastDate: maxToDate,
|
||||||
);
|
);
|
||||||
@@ -236,8 +238,8 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
|||||||
itemCount: 3,
|
itemCount: 3,
|
||||||
itemBuilder: (_, __) => ListTile(
|
itemBuilder: (_, __) => ListTile(
|
||||||
leading: Shimmer.fromColors(
|
leading: Shimmer.fromColors(
|
||||||
baseColor: Colors.grey[300]!,
|
baseColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
highlightColor: Colors.grey[100]!,
|
highlightColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
radius: 12,
|
radius: 12,
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
@@ -245,8 +247,8 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Shimmer.fromColors(
|
title: Shimmer.fromColors(
|
||||||
baseColor: Colors.grey[300]!,
|
baseColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
highlightColor: Colors.grey[100]!,
|
highlightColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 10,
|
height: 10,
|
||||||
width: 100,
|
width: 100,
|
||||||
@@ -255,8 +257,8 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: Shimmer.fromColors(
|
subtitle: Shimmer.fromColors(
|
||||||
baseColor: Colors.grey[300]!,
|
baseColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
highlightColor: Colors.grey[100]!,
|
highlightColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 8,
|
height: 8,
|
||||||
width: 60,
|
width: 60,
|
||||||
@@ -286,7 +288,7 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
|||||||
? Symbols.call_received
|
? Symbols.call_received
|
||||||
: Symbols.call_made,
|
: Symbols.call_made,
|
||||||
color: tx.type == 'CR'
|
color: tx.type == 'CR'
|
||||||
? Colors.green
|
? Theme.of(context).colorScheme.secondary
|
||||||
: Theme.of(context).colorScheme.error,
|
: Theme.of(context).colorScheme.error,
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
@@ -330,7 +332,7 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, index) {
|
separatorBuilder: (context, index) {
|
||||||
return const Divider();
|
return Divider(color: Theme.of(context).dividerColor);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ class TransactionDetailsScreen extends StatelessWidget {
|
|||||||
isCredit
|
isCredit
|
||||||
? Symbols.call_received
|
? Symbols.call_received
|
||||||
: Symbols.call_made,
|
: Symbols.call_made,
|
||||||
color: isCredit ? Colors.green : Colors.red,
|
color: isCredit
|
||||||
|
? Theme.of(context).colorScheme.secondary
|
||||||
|
: Theme.of(context).colorScheme.error,
|
||||||
size: 28,
|
size: 28,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -51,9 +53,9 @@ class TransactionDetailsScreen extends StatelessWidget {
|
|||||||
// Date centered
|
// Date centered
|
||||||
Text(
|
Text(
|
||||||
transaction.date ?? "",
|
transaction.date ?? "",
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: Colors.grey,
|
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
@@ -61,7 +63,7 @@ class TransactionDetailsScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(),
|
Divider(color: Theme.of(context).dividerColor),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 5,
|
flex: 5,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
|
|||||||
@@ -21,21 +21,47 @@ class _ManageBeneficiariesScreen extends State<ManageBeneficiariesScreen> {
|
|||||||
var service = getIt<BeneficiaryService>();
|
var service = getIt<BeneficiaryService>();
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
List<Beneficiary> _beneficiaries = [];
|
List<Beneficiary> _beneficiaries = [];
|
||||||
|
List<Beneficiary> _filteredBeneficiaries = [];
|
||||||
|
final TextEditingController _searchController = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_loadBeneficiaries();
|
_loadBeneficiaries();
|
||||||
|
_searchController.addListener(() {
|
||||||
|
_filterBeneficiaries(_searchController.text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_searchController.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadBeneficiaries() async {
|
Future<void> _loadBeneficiaries() async {
|
||||||
final data = await service.fetchBeneficiaryList();
|
final data = await service.fetchBeneficiaryList();
|
||||||
setState(() {
|
setState(() {
|
||||||
_beneficiaries = data;
|
_beneficiaries = data;
|
||||||
|
_filteredBeneficiaries = data;
|
||||||
_isLoading = false;
|
_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() {
|
Widget _buildShimmerList() {
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
itemCount: 6,
|
itemCount: 6,
|
||||||
@@ -63,40 +89,43 @@ class _ManageBeneficiariesScreen extends State<ManageBeneficiariesScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBeneficiaryList() {
|
Widget _buildBeneficiaryList() {
|
||||||
if (_beneficiaries.isEmpty) {
|
if (_filteredBeneficiaries.isEmpty) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Text(AppLocalizations.of(context).noBeneficiaryFound));
|
child: Text(AppLocalizations.of(context).noBeneficiaryFound));
|
||||||
}
|
}
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
itemCount: _beneficiaries.length,
|
itemCount: _filteredBeneficiaries.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final item = _beneficiaries[index];
|
final item = _filteredBeneficiaries[index];
|
||||||
return ListTile(
|
return Card(
|
||||||
leading: CircleAvatar(
|
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
radius: 24,
|
child: ListTile(
|
||||||
backgroundColor: Colors.transparent,
|
leading: CircleAvatar(
|
||||||
child: getBankLogo(item.bankName, context),
|
radius: 24,
|
||||||
),
|
backgroundColor: Colors.transparent,
|
||||||
title: Text(item.name),
|
child: getBankLogo(item.bankName, context),
|
||||||
subtitle: Column(
|
),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
title: Text(item.name),
|
||||||
children: [
|
subtitle: Column(
|
||||||
Text(item.accountNo),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
if (item.bankName != null && item.bankName!.isNotEmpty)
|
children: [
|
||||||
Text(
|
Text(item.accountNo),
|
||||||
item.bankName!,
|
if (item.bankName != null && item.bankName!.isNotEmpty)
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
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(
|
body: Stack(
|
||||||
children: [
|
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(
|
IgnorePointer(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Opacity(
|
child: Opacity(
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class _CardManagementScreen extends State<CardManagementScreen> {
|
|||||||
onTap: () {},
|
onTap: () {},
|
||||||
disabled: true, // Add this
|
disabled: true, // Add this
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||||
CardManagementTile(
|
CardManagementTile(
|
||||||
icon: Symbols.remove_moderator,
|
icon: Symbols.remove_moderator,
|
||||||
label: AppLocalizations.of(context).blockUnblockCard,
|
label: AppLocalizations.of(context).blockUnblockCard,
|
||||||
@@ -49,7 +49,7 @@ class _CardManagementScreen extends State<CardManagementScreen> {
|
|||||||
},
|
},
|
||||||
disabled: true,
|
disabled: true,
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||||
CardManagementTile(
|
CardManagementTile(
|
||||||
icon: Symbols.password_2,
|
icon: Symbols.password_2,
|
||||||
label: AppLocalizations.of(context).changeCardPin,
|
label: AppLocalizations.of(context).changeCardPin,
|
||||||
@@ -63,7 +63,7 @@ class _CardManagementScreen extends State<CardManagementScreen> {
|
|||||||
},
|
},
|
||||||
disabled: true,
|
disabled: true,
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||||
CardManagementTile(
|
CardManagementTile(
|
||||||
icon: Symbols.payment_card,
|
icon: Symbols.payment_card,
|
||||||
label: AppLocalizations.of(context).viewCardDeatils,
|
label: AppLocalizations.of(context).viewCardDeatils,
|
||||||
@@ -77,7 +77,7 @@ class _CardManagementScreen extends State<CardManagementScreen> {
|
|||||||
},
|
},
|
||||||
disabled: true,
|
disabled: true,
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
IgnorePointer(
|
IgnorePointer(
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
|
|||||||
title: Text(
|
title: Text(
|
||||||
AppLocalizations.of(context).chequeManagement,
|
AppLocalizations.of(context).chequeManagement,
|
||||||
style:
|
style:
|
||||||
const TextStyle(color: Colors.black, fontWeight: FontWeight.w500),
|
TextStyle(color: Theme.of(context).textTheme.titleLarge?.color, fontWeight: FontWeight.w500),
|
||||||
),
|
),
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
),
|
),
|
||||||
@@ -32,7 +32,7 @@ class _ChequeManagementScreen extends State<ChequeManagementScreen> {
|
|||||||
label: AppLocalizations.of(context).requestChequeBook,
|
label: AppLocalizations.of(context).requestChequeBook,
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||||
ChequeManagementTile(
|
ChequeManagementTile(
|
||||||
icon: Symbols.data_alert,
|
icon: Symbols.data_alert,
|
||||||
label: AppLocalizations.of(context).enquiry,
|
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(
|
ChequeManagementTile(
|
||||||
icon: Symbols.approval_delegation,
|
icon: Symbols.approval_delegation,
|
||||||
label: AppLocalizations.of(context).chequeDeposit,
|
label: AppLocalizations.of(context).chequeDeposit,
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||||
ChequeManagementTile(
|
ChequeManagementTile(
|
||||||
icon: Symbols.front_hand,
|
icon: Symbols.front_hand,
|
||||||
label: AppLocalizations.of(context).stopCheque,
|
label: AppLocalizations.of(context).stopCheque,
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||||
ChequeManagementTile(
|
ChequeManagementTile(
|
||||||
icon: Symbols.cancel_presentation,
|
icon: Symbols.cancel_presentation,
|
||||||
label: AppLocalizations.of(context).revokeStop,
|
label: AppLocalizations.of(context).revokeStop,
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||||
ChequeManagementTile(
|
ChequeManagementTile(
|
||||||
icon: Symbols.payments,
|
icon: Symbols.payments,
|
||||||
label: AppLocalizations.of(context).positivePay,
|
label: AppLocalizations.of(context).positivePay,
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
IgnorePointer(
|
IgnorePointer(
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ class _CustomerInfoScreenState extends State<CustomerInfoScreen> {
|
|||||||
value: user.branchId ?? 'N/A',
|
value: user.branchId ?? 'N/A',
|
||||||
),
|
),
|
||||||
InfoField(
|
InfoField(
|
||||||
label: AppLocalizations.of(context).branchAddress,
|
label: AppLocalizations.of(context).address,
|
||||||
value: user.address ?? 'N/A',
|
value: user.address ?? 'N/A',
|
||||||
), // Replace with Aadhar if available
|
), // Replace with Aadhar if available
|
||||||
InfoField(
|
InfoField(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:developer';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_svg/svg.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/data/repositories/transaction_repository.dart';
|
||||||
import 'package:kmobile/di/injection.dart';
|
import 'package:kmobile/di/injection.dart';
|
||||||
import 'package:kmobile/features/accounts/screens/account_info_screen.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_cubit.dart';
|
||||||
import 'package:kmobile/features/auth/controllers/auth_state.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/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/beneficiaries/screens/manage_beneficiaries_screen.dart';
|
||||||
import 'package:kmobile/features/enquiry/screens/enquiry_screen.dart';
|
import 'package:kmobile/features/enquiry/screens/enquiry_screen.dart';
|
||||||
import 'package:kmobile/features/fund_transfer/screens/fund_transfer_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>
|
class _DashboardScreenState extends State<DashboardScreen>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
int selectedAccountIndex = 0;
|
int selectedAccountIndex = 0;
|
||||||
bool isVisible = false;
|
Map<String, bool> _visibilityMap = {};
|
||||||
bool isRefreshing = false;
|
bool isRefreshing = false;
|
||||||
bool isBalanceLoading = false;
|
bool isBalanceLoading = false;
|
||||||
bool _biometricPromptShown = false;
|
bool _biometricPromptShown = false;
|
||||||
bool _txLoading = false;
|
bool _txLoading = false;
|
||||||
List<Transaction> _transactions = [];
|
List<Transaction> _transactions = [];
|
||||||
bool _txInitialized = false;
|
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 {
|
Future<void> _loadTransactions(String accountNo) async {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -103,7 +259,7 @@ class _DashboardScreenState extends State<DashboardScreen>
|
|||||||
baseColor: theme.colorScheme.primary,
|
baseColor: theme.colorScheme.primary,
|
||||||
highlightColor: theme.colorScheme.onPrimary,
|
highlightColor: theme.colorScheme.onPrimary,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 200, height: 42, color: theme.scaffoldBackgroundColor),
|
height: 36, color: theme.scaffoldBackgroundColor),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,12 +378,17 @@ class _DashboardScreenState extends State<DashboardScreen>
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
final authState = context.read<AuthCubit>().state;
|
final authState = context.read<AuthCubit>().state;
|
||||||
String mobileNumberToPass = '';
|
String mobileNumberToPass = '';
|
||||||
|
String customerNo ='';
|
||||||
|
String customerName = '';
|
||||||
if (authState is Authenticated) {
|
if (authState is Authenticated) {
|
||||||
if (selectedAccountIndex >= 0 &&
|
if (selectedAccountIndex >= 0 &&
|
||||||
selectedAccountIndex < authState.users.length) {
|
selectedAccountIndex < authState.users.length) {
|
||||||
mobileNumberToPass =
|
mobileNumberToPass =
|
||||||
authState.users[selectedAccountIndex].mobileNo ?? '';
|
authState.users[selectedAccountIndex].mobileNo ?? '';
|
||||||
|
customerNo =
|
||||||
|
authState.users[selectedAccountIndex].cifNumber ?? '';
|
||||||
|
customerName=
|
||||||
|
authState.users[selectedAccountIndex].name ?? '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +396,7 @@ class _DashboardScreenState extends State<DashboardScreen>
|
|||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
ProfileScreen(mobileNumber: mobileNumberToPass),
|
ProfileScreen(mobileNumber: mobileNumberToPass, customerNo: customerNo, customerName: customerName),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -280,10 +441,13 @@ class _DashboardScreenState extends State<DashboardScreen>
|
|||||||
_loadTransactions(currAccount.accountNo!);
|
_loadTransactions(currAccount.accountNo!);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
_pageController ??= PageController(
|
||||||
|
initialPage: selectedAccountIndex,
|
||||||
|
viewportFraction: 0.85,
|
||||||
|
);
|
||||||
final firstName = getProcessedFirstName(currAccount.name);
|
final firstName = getProcessedFirstName(currAccount.name);
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -302,162 +466,34 @@ class _DashboardScreenState extends State<DashboardScreen>
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Account Info Card
|
// Account Info Cards
|
||||||
Container(
|
SizedBox(
|
||||||
padding: const EdgeInsets.symmetric(
|
height: 160, // Adjusted height
|
||||||
horizontal: 18,
|
child: PageView.builder(
|
||||||
vertical: 10,
|
controller: _pageController,
|
||||||
),
|
itemCount: users.length,
|
||||||
decoration: BoxDecoration(
|
onPageChanged: (int newIndex) async {
|
||||||
color: Color(0xFF01A04C),
|
if (newIndex == selectedAccountIndex) return;
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
// Hide the balance of the old card when scrolling away
|
||||||
child: Column(
|
final oldAccountNo = users[selectedAccountIndex].accountNo;
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
if (oldAccountNo != null) {
|
||||||
children: [
|
_visibilityMap[oldAccountNo] = false;
|
||||||
Row(
|
}
|
||||||
children: [
|
|
||||||
Text(
|
setState(() {
|
||||||
"${getFullAccountType(currAccount.accountType)}: ",
|
selectedAccountIndex = newIndex;
|
||||||
style: TextStyle(
|
});
|
||||||
color: theme.colorScheme.onPrimary,
|
|
||||||
fontSize: 18,
|
await _loadTransactions(
|
||||||
fontWeight: FontWeight.w700,
|
users[newIndex].accountNo!,
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
DropdownButton<int>(
|
itemBuilder: (context, index) {
|
||||||
value: selectedAccountIndex,
|
final user = users[index];
|
||||||
dropdownColor: theme.colorScheme.primary,
|
final isSelected = index == selectedAccountIndex;
|
||||||
underline: const SizedBox(),
|
return _buildAccountCard(user, isSelected);
|
||||||
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),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 18),
|
const SizedBox(height: 18),
|
||||||
@@ -465,13 +501,16 @@ class _DashboardScreenState extends State<DashboardScreen>
|
|||||||
AppLocalizations.of(context).quickLinks,
|
AppLocalizations.of(context).quickLinks,
|
||||||
style: const TextStyle(fontSize: 17),
|
style: const TextStyle(fontSize: 17),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
// Quick Links
|
// Quick Links
|
||||||
GridView.count(
|
GridView.count(
|
||||||
crossAxisCount: 4,
|
crossAxisCount: 3,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
crossAxisSpacing: 10,
|
||||||
|
mainAxisSpacing: 10,
|
||||||
|
childAspectRatio: 1,
|
||||||
children: [
|
children: [
|
||||||
_buildQuickLink(
|
_buildQuickLink(
|
||||||
Symbols.id_card,
|
Symbols.id_card,
|
||||||
@@ -574,69 +613,22 @@ class _DashboardScreenState extends State<DashboardScreen>
|
|||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
const EnquiryScreen()));
|
const EnquiryScreen()));
|
||||||
}),
|
}),
|
||||||
],
|
_buildQuickLink(
|
||||||
),
|
Symbols.request_quote,
|
||||||
const SizedBox(height: 5),
|
AppLocalizations.of(context).chequeManagement,
|
||||||
|
() {
|
||||||
// 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: () {
|
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) =>
|
builder: (context) =>
|
||||||
TransactionDetailsScreen(transaction: tx),
|
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -684,28 +676,40 @@ class _DashboardScreenState extends State<DashboardScreen>
|
|||||||
bool disable = false,
|
bool disable = false,
|
||||||
}) {
|
}) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return InkWell(
|
return Card(
|
||||||
onTap: disable ? null : onTap,
|
elevation: 2,
|
||||||
child: Column(
|
shape: RoundedRectangleBorder(
|
||||||
mainAxisSize: MainAxisSize.min,
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
children: [
|
),
|
||||||
Icon(
|
child: InkWell(
|
||||||
icon,
|
onTap: disable ? null : onTap,
|
||||||
size: 30,
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
color: disable
|
child: Column(
|
||||||
? theme.colorScheme.onSurface.withOpacity(0.3)
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
: theme.colorScheme.primary,
|
children: [
|
||||||
grade: 200,
|
Icon(
|
||||||
weight: 700,
|
icon,
|
||||||
),
|
size: 30,
|
||||||
const SizedBox(height: 4),
|
color: disable
|
||||||
Text(
|
? theme.disabledColor
|
||||||
label,
|
: theme.colorScheme.primary,
|
||||||
textAlign: TextAlign.center,
|
),
|
||||||
style: const TextStyle(fontSize: 13),
|
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>();
|
var service = getIt<BeneficiaryService>();
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
List<Beneficiary> _beneficiaries = [];
|
List<Beneficiary> _beneficiaries = [];
|
||||||
|
List<Beneficiary> _filteredBeneficiaries = [];
|
||||||
|
final TextEditingController _searchController = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_loadBeneficiaries();
|
_loadBeneficiaries();
|
||||||
|
_searchController.addListener(() {
|
||||||
|
_filterBeneficiaries(_searchController.text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_searchController.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadBeneficiaries() async {
|
Future<void> _loadBeneficiaries() async {
|
||||||
@@ -43,10 +54,25 @@ class _FundTransferBeneficiaryScreenState
|
|||||||
? b.bankName!.toLowerCase().contains('kangra central')
|
? b.bankName!.toLowerCase().contains('kangra central')
|
||||||
: !b.bankName!.toLowerCase().contains('kangra central'))
|
: !b.bankName!.toLowerCase().contains('kangra central'))
|
||||||
.toList();
|
.toList();
|
||||||
|
_filteredBeneficiaries = _beneficiaries;
|
||||||
_isLoading = false;
|
_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() {
|
Widget _buildShimmerList() {
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
itemCount: 6,
|
itemCount: 6,
|
||||||
@@ -74,14 +100,14 @@ class _FundTransferBeneficiaryScreenState
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBeneficiaryList() {
|
Widget _buildBeneficiaryList() {
|
||||||
if (_beneficiaries.isEmpty) {
|
if (_filteredBeneficiaries.isEmpty) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Text(AppLocalizations.of(context).noBeneficiaryFound));
|
child: Text(AppLocalizations.of(context).noBeneficiaryFound));
|
||||||
}
|
}
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
itemCount: _beneficiaries.length,
|
itemCount: _filteredBeneficiaries.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final beneficiary = _beneficiaries[index];
|
final beneficiary = _filteredBeneficiaries[index];
|
||||||
|
|
||||||
// --- Cooldown Logic ---
|
// --- Cooldown Logic ---
|
||||||
bool isCoolingDown = false;
|
bool isCoolingDown = false;
|
||||||
@@ -94,60 +120,63 @@ class _FundTransferBeneficiaryScreenState
|
|||||||
|
|
||||||
// By wrapping the ListTile in an Opacity widget, we can make it look
|
// By wrapping the ListTile in an Opacity widget, we can make it look
|
||||||
// disabled while ensuring the onTap callback still works.
|
// disabled while ensuring the onTap callback still works.
|
||||||
return Opacity(
|
return Card(
|
||||||
opacity: isCoolingDown ? 0.5 : 1.0,
|
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
child: ListTile(
|
child: Opacity(
|
||||||
// REMOVED the 'enabled' property from here.
|
opacity: isCoolingDown ? 0.5 : 1.0,
|
||||||
leading: CircleAvatar(
|
child: ListTile(
|
||||||
radius: 24,
|
// REMOVED the 'enabled' property from here.
|
||||||
backgroundColor: Colors.transparent,
|
leading: CircleAvatar(
|
||||||
child: getBankLogo(beneficiary.bankName, context),
|
radius: 24,
|
||||||
),
|
backgroundColor: Colors.transparent,
|
||||||
title: Text(beneficiary.name),
|
child: getBankLogo(beneficiary.bankName, context),
|
||||||
subtitle: Column(
|
),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
title: Text(beneficiary.name),
|
||||||
children: [
|
subtitle: Column(
|
||||||
Text(beneficiary.accountNo),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
if (beneficiary.bankName != null &&
|
children: [
|
||||||
beneficiary.bankName!.isNotEmpty)
|
Text(beneficiary.accountNo),
|
||||||
Text(
|
if (beneficiary.bankName != null &&
|
||||||
beneficiary.bankName!,
|
beneficiary.bankName!.isNotEmpty)
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
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,
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
);
|
),
|
||||||
}
|
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(
|
body: Stack(
|
||||||
children: [
|
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(
|
IgnorePointer(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Opacity(
|
child: Opacity(
|
||||||
|
|||||||
@@ -34,66 +34,73 @@ class FundTransferScreen extends StatelessWidget {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
ListView(
|
Padding(
|
||||||
children: [
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
FundTransferManagementTile(
|
child: Column(
|
||||||
icon: Symbols.person,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
// Restore localization for the label
|
children: [
|
||||||
label: "Self Pay",
|
Expanded(
|
||||||
onTap: () {
|
child: FundTransferManagementTile(
|
||||||
// The accounts list is passed directly from the constructor
|
icon: Symbols.person,
|
||||||
Navigator.push(
|
label: "Self Pay",
|
||||||
context,
|
onTap: () {
|
||||||
MaterialPageRoute(
|
Navigator.push(
|
||||||
builder: (context) => FundTransferSelfAccountsScreen(
|
context,
|
||||||
debitAccountNo: creditAccountNo,
|
MaterialPageRoute(
|
||||||
remitterName: remitterName,
|
builder: (context) =>
|
||||||
accounts: accounts,
|
FundTransferSelfAccountsScreen(
|
||||||
),
|
debitAccountNo: creditAccountNo,
|
||||||
),
|
remitterName: remitterName,
|
||||||
);
|
accounts: accounts,
|
||||||
},
|
),
|
||||||
// Disable the tile if the state is not Authenticated
|
),
|
||||||
disable: state is! Authenticated,
|
);
|
||||||
),
|
},
|
||||||
const Divider(height: 1),
|
disable: state is! Authenticated,
|
||||||
FundTransferManagementTile(
|
),
|
||||||
icon: Symbols.input_circle,
|
),
|
||||||
// Restore localization for the label
|
const SizedBox(height: 16),
|
||||||
label: AppLocalizations.of(context).ownBank,
|
Expanded(
|
||||||
onTap: () {
|
child: FundTransferManagementTile(
|
||||||
Navigator.push(
|
icon: Symbols.input_circle,
|
||||||
context,
|
label: AppLocalizations.of(context).ownBank,
|
||||||
MaterialPageRoute(
|
onTap: () {
|
||||||
builder: (context) => FundTransferBeneficiaryScreen(
|
Navigator.push(
|
||||||
creditAccountNo: creditAccountNo,
|
context,
|
||||||
remitterName: remitterName,
|
MaterialPageRoute(
|
||||||
isOwnBank: true,
|
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: () {
|
const SizedBox(height: 16),
|
||||||
Navigator.push(
|
Expanded(
|
||||||
context,
|
child: FundTransferManagementTile(
|
||||||
MaterialPageRoute(
|
icon: Symbols.output_circle,
|
||||||
builder: (context) => FundTransferBeneficiaryScreen(
|
label: AppLocalizations.of(context).outsideBank,
|
||||||
creditAccountNo: creditAccountNo,
|
onTap: () {
|
||||||
remitterName: remitterName,
|
Navigator.push(
|
||||||
isOwnBank: false,
|
context,
|
||||||
),
|
MaterialPageRoute(
|
||||||
),
|
builder: (context) =>
|
||||||
);
|
FundTransferBeneficiaryScreen(
|
||||||
},
|
creditAccountNo: creditAccountNo,
|
||||||
),
|
remitterName: remitterName,
|
||||||
const Divider(height: 1),
|
isOwnBank: false,
|
||||||
],
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
IgnorePointer(
|
IgnorePointer(
|
||||||
child: Center(
|
child: Center(
|
||||||
@@ -133,12 +140,44 @@ class FundTransferManagementTile extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListTile(
|
final theme = Theme.of(context);
|
||||||
leading: Icon(icon),
|
return Card(
|
||||||
title: Text(label),
|
margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||||
trailing: const Icon(Symbols.arrow_right, size: 20),
|
shape: RoundedRectangleBorder(
|
||||||
onTap: onTap,
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
enabled: !disable,
|
),
|
||||||
|
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,
|
itemCount: filteredAccounts.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final account = filteredAccounts[index];
|
final account = filteredAccounts[index];
|
||||||
return ListTile(
|
return Card(
|
||||||
leading: CircleAvatar(
|
margin: const EdgeInsets.symmetric(
|
||||||
radius: 24,
|
horizontal: 12, vertical: 6),
|
||||||
backgroundColor: Colors.transparent,
|
child: ListTile(
|
||||||
child: getBankLogo(
|
leading: CircleAvatar(
|
||||||
'Kangra Central Co-operative Bank', context),
|
radius: 24,
|
||||||
),
|
backgroundColor: Colors.transparent,
|
||||||
title: Text(account.name ?? 'N/A'),
|
child: getBankLogo(
|
||||||
subtitle: Column(
|
'Kangra Central Co-operative Bank', context),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
),
|
||||||
children: [
|
title: Text(account.name ?? 'N/A'),
|
||||||
Text(account.accountNo ?? 'N/A'),
|
subtitle: Column(
|
||||||
Text(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
_getFullAccountType(account.accountType),
|
children: [
|
||||||
style: TextStyle(
|
Text(account.accountNo ?? 'N/A'),
|
||||||
fontSize: 12, color: Colors.grey[600]),
|
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,
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
);
|
),
|
||||||
},
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ import 'package:kmobile/features/profile/preferences/preference_screen.dart';
|
|||||||
|
|
||||||
class ProfileScreen extends StatefulWidget {
|
class ProfileScreen extends StatefulWidget {
|
||||||
final String mobileNumber;
|
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
|
@override
|
||||||
State<ProfileScreen> createState() => _ProfileScreenState();
|
State<ProfileScreen> createState() => _ProfileScreenState();
|
||||||
@@ -157,149 +159,463 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
Widget build(BuildContext context) {
|
// Widget build(BuildContext context) {
|
||||||
final loc = AppLocalizations.of(context);
|
// final loc = AppLocalizations.of(context);
|
||||||
|
|
||||||
return Scaffold(
|
// return Scaffold(
|
||||||
appBar: AppBar(
|
// appBar: AppBar(
|
||||||
title: Text(loc.profile), // Localized "Profile"
|
// title: Text(loc.profile), // Localized "Profile"
|
||||||
),
|
// ),
|
||||||
body: Stack(
|
// body: Stack(
|
||||||
children: [
|
// children: [
|
||||||
ListView(
|
// ListView(
|
||||||
children: [
|
// children: [
|
||||||
ListTile(
|
// ListTile(
|
||||||
leading: const Icon(Icons.settings),
|
// leading: const Icon(Icons.settings),
|
||||||
title: Text(loc.preferences),
|
// title: Text(loc.preferences),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
// trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () {
|
// onTap: () {
|
||||||
Navigator.push(
|
// Navigator.push(
|
||||||
context,
|
// context,
|
||||||
MaterialPageRoute(
|
// MaterialPageRoute(
|
||||||
builder: (context) => const PreferenceScreen()),
|
// builder: (context) => const PreferenceScreen()),
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
ListTile(
|
// ListTile(
|
||||||
leading: const Icon(Icons.security),
|
// leading: const Icon(Icons.security),
|
||||||
title: Text(loc.securitySettings),
|
// title: Text(loc.securitySettings),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
// trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () {
|
// onTap: () {
|
||||||
Navigator.push(
|
// Navigator.push(
|
||||||
context,
|
// context,
|
||||||
MaterialPageRoute(
|
// MaterialPageRoute(
|
||||||
builder: (context) => SecuritySettingsScreen(
|
// builder: (context) => SecuritySettingsScreen(
|
||||||
mobileNumber: widget.mobileNumber,
|
// mobileNumber: widget.mobileNumber,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// ListTile(
|
||||||
|
// leading: const Icon(Icons.currency_rupee),
|
||||||
|
// title: Text(AppLocalizations.of(context).dailylimit),
|
||||||
|
// onTap: () {
|
||||||
|
// Navigator.push(
|
||||||
|
// context,
|
||||||
|
// MaterialPageRoute(
|
||||||
|
// 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),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ListTile(
|
||||||
|
// leading: const Icon(Icons.exit_to_app),
|
||||||
|
// title: Text(AppLocalizations.of(context).logout),
|
||||||
|
// onTap: () async {
|
||||||
|
// final shouldExit = await showDialog<bool>(
|
||||||
|
// context: context,
|
||||||
|
// builder: (context) => AlertDialog(
|
||||||
|
// title: Text(AppLocalizations.of(context).logout),
|
||||||
|
// content: Text(AppLocalizations.of(context).logoutCheck),
|
||||||
|
// actions: [
|
||||||
|
// TextButton(
|
||||||
|
// onPressed: () => Navigator.of(context).pop(false),
|
||||||
|
// child: Text(AppLocalizations.of(context).no),
|
||||||
|
// ),
|
||||||
|
// TextButton(
|
||||||
|
// onPressed: () => Navigator.of(context).pop(true),
|
||||||
|
// child: Text(AppLocalizations.of(context).yes),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// if (shouldExit == true) {
|
||||||
|
// if (Platform.isAndroid) {
|
||||||
|
// SystemNavigator.pop();
|
||||||
|
// }
|
||||||
|
// exit(0);
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// ListTile(
|
||||||
|
// leading: const Icon(Icons.logout),
|
||||||
|
// title: Text(AppLocalizations.of(context).deregister),
|
||||||
|
// onTap: () async {
|
||||||
|
// final shouldLogout = await showDialog<bool>(
|
||||||
|
// context: context,
|
||||||
|
// builder: (_) => const LogoutDialog(),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// if (shouldLogout == true) {
|
||||||
|
// await _handleLogout(context);
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// 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) {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final isDarkMode = theme.brightness == Brightness.dark;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(loc.profile),
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
ListView(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
children: [
|
||||||
|
// ===== 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
),
|
||||||
leading: const Icon(Icons.currency_rupee),
|
|
||||||
title: Text(AppLocalizations.of(context).dailylimit),
|
const SizedBox(height: 16),
|
||||||
onTap: () {
|
|
||||||
Navigator.push(
|
// ===== Section: Settings =====
|
||||||
context,
|
Padding(
|
||||||
MaterialPageRoute(
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
builder: (context) => const DailyLimitScreen()),
|
child: Text(
|
||||||
);
|
"Settings",
|
||||||
},
|
style: theme.textTheme.labelLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
letterSpacing: 0.2,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
),
|
||||||
title:
|
const SizedBox(height: 8),
|
||||||
Text(AppLocalizations.of(context).enableFingerprintLogin),
|
|
||||||
|
_SectionTile(
|
||||||
|
leadingIcon: Icons.settings,
|
||||||
|
title: loc.preferences,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const PreferenceScreen(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_SectionTile(
|
||||||
|
leadingIcon: Icons.security,
|
||||||
|
title: loc.securitySettings,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => SecuritySettingsScreen(
|
||||||
|
mobileNumber: widget.mobileNumber,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_SectionTile(
|
||||||
|
leadingIcon: Icons.currency_rupee,
|
||||||
|
title: loc.dailylimit,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const DailyLimitScreen(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Card(
|
||||||
|
child: SwitchListTile(
|
||||||
|
title: Text(loc.enableFingerprintLogin),
|
||||||
value: _isBiometricEnabled,
|
value: _isBiometricEnabled,
|
||||||
onChanged: (bool value) {
|
onChanged: (bool value) {
|
||||||
// The state is now managed within _handleBiometricToggle
|
|
||||||
_handleBiometricToggle(value);
|
_handleBiometricToggle(value);
|
||||||
},
|
},
|
||||||
secondary: const Icon(Icons.fingerprint),
|
secondary: const Icon(Icons.fingerprint),
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||||
),
|
),
|
||||||
ListTile(
|
),
|
||||||
|
|
||||||
|
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),
|
leading: const Icon(Icons.smartphone),
|
||||||
title: const Text("App Version"),
|
title: Text(loc.appVersion),
|
||||||
trailing: FutureBuilder<String>(
|
trailing: FutureBuilder<String>(
|
||||||
future: _getAppVersion(),
|
future: _getAppVersion(),
|
||||||
builder:
|
builder:
|
||||||
(BuildContext context, AsyncSnapshot<String> snapshot) {
|
(BuildContext context, AsyncSnapshot<String> snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
// Show a loading indicator while waiting for the future to complete
|
return const SizedBox(
|
||||||
return const CircularProgressIndicator();
|
width: 18,
|
||||||
|
height: 18,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
|
);
|
||||||
} else if (snapshot.hasError) {
|
} else if (snapshot.hasError) {
|
||||||
return const Text("Error");
|
return Text(loc.error);
|
||||||
} else {
|
} else {
|
||||||
// Display the version number once the future is complete
|
|
||||||
return Text(
|
return Text(
|
||||||
snapshot.data ?? "N/A",
|
snapshot.data ?? "N/A",
|
||||||
selectionColor: const Color(0xFFFFFFFF),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
),
|
||||||
leading: const Icon(Icons.exit_to_app),
|
|
||||||
title: Text(AppLocalizations.of(context).logout),
|
|
||||||
onTap: () async {
|
|
||||||
final shouldExit = await showDialog<bool>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: Text(AppLocalizations.of(context).logout),
|
|
||||||
content: Text(AppLocalizations.of(context).logoutCheck),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
|
||||||
child: Text(AppLocalizations.of(context).no),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
|
||||||
child: Text(AppLocalizations.of(context).yes),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (shouldExit == true) {
|
const SizedBox(height: 16),
|
||||||
if (Platform.isAndroid) {
|
const Divider(height: 24),
|
||||||
SystemNavigator.pop();
|
|
||||||
}
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.logout),
|
|
||||||
title: Text(AppLocalizations.of(context).deregister),
|
|
||||||
onTap: () async {
|
|
||||||
final shouldLogout = await showDialog<bool>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => const LogoutDialog(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (shouldLogout == true) {
|
// ===== Section: Actions =====
|
||||||
await _handleLogout(context);
|
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),
|
||||||
IgnorePointer(
|
|
||||||
|
_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(loc.logout),
|
||||||
|
content: Text(loc.logoutCheck),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
|
child: Text(loc.no),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
|
child: Text(loc.yes),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (shouldExit == true) {
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
SystemNavigator.pop();
|
||||||
|
}
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_SectionTile(
|
||||||
|
leadingIcon: Icons.logout,
|
||||||
|
title: loc.deregister,
|
||||||
|
trailChevron: false,
|
||||||
|
onTap: () async {
|
||||||
|
final shouldLogout = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => const LogoutDialog(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (shouldLogout == true) {
|
||||||
|
await _handleLogout(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// ===== Watermark (kept subtle, no theme change) =====
|
||||||
|
IgnorePointer(
|
||||||
|
child: Positioned.fill(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: 0.07, // Reduced opacity
|
opacity: 0.06,
|
||||||
child: ClipOval(
|
child: ClipOval(
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
'assets/images/logo.png',
|
'assets/images/logo.png',
|
||||||
width: 200, // Adjust size as needed
|
width: 200,
|
||||||
height: 200, // Adjust size as needed
|
height: 200,
|
||||||
|
filterQuality: FilterQuality.medium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class SecuritySettingsScreen extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.pin),
|
leading: const Icon(Icons.pin),
|
||||||
title: Text(loc.changeMpin),
|
title: Text(loc.changeMpin),
|
||||||
@@ -57,13 +57,13 @@ class SecuritySettingsScreen extends StatelessWidget {
|
|||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(loc.mpinChangedSuccessfully),
|
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(
|
ListTile(
|
||||||
leading: const Icon(Icons.password),
|
leading: const Icon(Icons.password),
|
||||||
title: const Text('Change TPIN'),
|
title: const Text('Change TPIN'),
|
||||||
|
|||||||
@@ -23,39 +23,46 @@ class _QuickPayScreen extends State<QuickPayScreen> {
|
|||||||
),
|
),
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
ListView(
|
Padding(
|
||||||
children: [
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
QuickPayManagementTile(
|
child: Column(
|
||||||
icon: Symbols.input_circle,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
label: AppLocalizations.of(context).ownBank,
|
children: [
|
||||||
onTap: () {
|
Expanded(
|
||||||
Navigator.push(
|
child: QuickPayManagementTile(
|
||||||
context,
|
icon: Symbols.input_circle,
|
||||||
MaterialPageRoute(
|
label: AppLocalizations.of(context).ownBank,
|
||||||
builder: (context) => QuickPayWithinBankScreen(
|
onTap: () {
|
||||||
debitAccount: widget.debitAccount,
|
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(
|
const SizedBox(height: 16),
|
||||||
context,
|
Expanded(
|
||||||
MaterialPageRoute(
|
child: QuickPayManagementTile(
|
||||||
builder: (context) => QuickPayOutsideBankScreen(
|
icon: Symbols.output_circle,
|
||||||
debitAccount: widget.debitAccount,
|
label: AppLocalizations.of(context).outsideBank,
|
||||||
),
|
onTap: () {
|
||||||
),
|
Navigator.push(
|
||||||
);
|
context,
|
||||||
},
|
MaterialPageRoute(
|
||||||
),
|
builder: (context) => QuickPayOutsideBankScreen(
|
||||||
const Divider(height: 1),
|
debitAccount: widget.debitAccount,
|
||||||
],
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
IgnorePointer(
|
IgnorePointer(
|
||||||
child: Center(
|
child: Center(
|
||||||
@@ -93,12 +100,42 @@ class QuickPayManagementTile extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListTile(
|
final theme = Theme.of(context);
|
||||||
leading: Icon(icon),
|
return Card(
|
||||||
title: Text(label),
|
margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||||
trailing: const Icon(Symbols.arrow_right, size: 20),
|
shape: RoundedRectangleBorder(
|
||||||
onTap: onTap,
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
enabled: !disable,
|
),
|
||||||
|
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(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildDetailRow("Branch Name", branch.branch_name),
|
_buildDetailRow(context, "Branch Name", branch.branch_name),
|
||||||
_buildDetailRow("Branch Code", branch.branch_code),
|
_buildDetailRow(context, "Branch Code", branch.branch_code),
|
||||||
_buildDetailRow("Zone", branch.zone),
|
_buildDetailRow(context, "Zone", branch.zone),
|
||||||
_buildDetailRow("Tehsil", branch.tehsil),
|
_buildDetailRow(context, "Tehsil", branch.tehsil),
|
||||||
_buildDetailRow("Block", branch.block),
|
_buildDetailRow(context, "Block", branch.block),
|
||||||
_buildDetailRow("District", branch.distt_name),
|
_buildDetailRow(context, "District", branch.distt_name),
|
||||||
_buildDetailRow("Pincode", branch.pincode),
|
_buildDetailRow(context, "Pincode", branch.pincode),
|
||||||
// _buildDetailRow("Post Office", branch.post_office),
|
// _buildDetailRow(context, "Post Office", branch.post_office),
|
||||||
// _buildDetailRow("Date of Opening", branch.date_of_opening),
|
// _buildDetailRow(context, "Date of Opening", branch.date_of_opening),
|
||||||
// _buildDetailRow("Branch Type", branch.type_of_branch),
|
// _buildDetailRow(context, "Branch Type", branch.type_of_branch),
|
||||||
_buildDetailRow("Telephone No.", branch.telephone_no),
|
_buildDetailRow(context, "Telephone No.", branch.telephone_no),
|
||||||
// _buildDetailRow("RTGS Account No.", branch.rtgs_acct_no),
|
// _buildDetailRow("RTGS Account No.", branch.rtgs_acct_no),
|
||||||
// _buildDetailRow("RBI Code 1", branch.rbi_code_1),
|
// _buildDetailRow("RBI Code 1", branch.rbi_code_1),
|
||||||
// _buildDetailRow("RBI Code 2", branch.rbi_code_2),
|
// _buildDetailRow("RBI Code 2", branch.rbi_code_2),
|
||||||
@@ -58,46 +58,46 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDetailRow(String label, String value) {
|
Widget _buildDetailRow(BuildContext context, String label, String value) {
|
||||||
return Padding(
|
final theme = Theme.of(context);
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
return Padding(
|
||||||
child: Column(
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(
|
children: [
|
||||||
label,
|
Text(
|
||||||
style: TextStyle(
|
label,
|
||||||
fontSize: 14,
|
style: TextStyle(
|
||||||
color: Colors.grey[600],
|
fontSize: 14,
|
||||||
|
color: theme.textTheme.bodySmall?.color,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 4),
|
||||||
const SizedBox(height: 4),
|
label == "Telephone No."
|
||||||
label == "Telephone No."
|
? InkWell(
|
||||||
? InkWell(
|
onTap: () => _launchUrl('tel:$value'),
|
||||||
onTap: () => _launchUrl('tel:$value'),
|
child: Text(
|
||||||
child: Text(
|
value,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: theme.colorScheme.primary, // Indicate it's clickable
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Text(
|
||||||
value,
|
value,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Colors.blue, // Indicate it's clickable
|
|
||||||
decoration: TextDecoration.underline,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
Divider(height: 16, color: theme.dividerColor),
|
||||||
: Text(
|
],
|
||||||
value,
|
),
|
||||||
style: const TextStyle(
|
);
|
||||||
fontSize: 16,
|
}
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Divider(height: 16),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _launchUrl(String urlString) async {
|
Future<void> _launchUrl(String urlString) async {
|
||||||
final Uri url = Uri.parse(urlString);
|
final Uri url = Uri.parse(urlString);
|
||||||
if (!await launchUrl(url)) {
|
if (!await launchUrl(url)) {
|
||||||
|
|||||||
@@ -56,7 +56,12 @@ class BranchLocatorScreen extends StatefulWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
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(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -27,58 +27,54 @@ class _ServiceScreen extends State<ServiceScreen> {
|
|||||||
),
|
),
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
ListView(
|
Padding(
|
||||||
children: [
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
// ServiceManagementTile(
|
child: Column(
|
||||||
// icon: Symbols.add,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
// label: AppLocalizations.of(context).accountOpeningDeposit,
|
children: [
|
||||||
// onTap: () {},
|
Expanded(
|
||||||
// disabled: true,
|
child: ServiceManagementTile(
|
||||||
// ),
|
icon: Symbols.captive_portal,
|
||||||
// const Divider(height: 1),
|
label: AppLocalizations.of(context).quickLinks,
|
||||||
// ServiceManagementTile(
|
onTap: () {
|
||||||
// icon: Symbols.add,
|
Navigator.of(context).push(
|
||||||
// label: AppLocalizations.of(context).accountOpeningLoan,
|
MaterialPageRoute(
|
||||||
// onTap: () {},
|
builder: (context) => const QuickLinksScreen()),
|
||||||
// disabled: true,
|
);
|
||||||
// ),
|
},
|
||||||
// const Divider(height: 1),
|
disabled: false,
|
||||||
ServiceManagementTile(
|
),
|
||||||
icon: Symbols.captive_portal,
|
),
|
||||||
label: AppLocalizations.of(context).quickLinks,
|
const SizedBox(height: 16),
|
||||||
onTap: () {
|
Expanded(
|
||||||
Navigator.of(context).push(
|
child: ServiceManagementTile(
|
||||||
MaterialPageRoute(
|
icon: Symbols.question_mark,
|
||||||
builder: (context) => const QuickLinksScreen()),
|
label: AppLocalizations.of(context).faq,
|
||||||
);
|
onTap: () {
|
||||||
},
|
Navigator.of(context).push(
|
||||||
disabled: false,
|
MaterialPageRoute(builder: (context) => const FaqsScreen()),
|
||||||
),
|
);
|
||||||
const Divider(height: 1),
|
},
|
||||||
ServiceManagementTile(
|
disabled: false,
|
||||||
icon: Symbols.question_mark,
|
),
|
||||||
label: AppLocalizations.of(context).faq,
|
),
|
||||||
onTap: () {
|
const SizedBox(height: 16),
|
||||||
Navigator.of(context).push(
|
Expanded(
|
||||||
MaterialPageRoute(builder: (context) => const FaqsScreen()),
|
child: ServiceManagementTile(
|
||||||
);
|
icon: Symbols.location_pin,
|
||||||
},
|
label: "ATM Locator",
|
||||||
disabled: false,
|
onTap: () {
|
||||||
),
|
Navigator.push(
|
||||||
const Divider(height: 1),
|
context,
|
||||||
ServiceManagementTile(
|
MaterialPageRoute(
|
||||||
icon: Symbols.location_pin,
|
builder: (context) => const ATMLocatorScreen()));
|
||||||
label: "ATM Locator",
|
},
|
||||||
onTap: () {
|
disabled: false,
|
||||||
Navigator.push(
|
),
|
||||||
context,
|
),
|
||||||
MaterialPageRoute(
|
// No Spacer() needed here as Expanded children will fill space
|
||||||
builder: (context) => const ATMLocatorScreen()));
|
],
|
||||||
},
|
),
|
||||||
disabled: false,
|
|
||||||
),
|
|
||||||
const Divider(height: 1),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
IgnorePointer(
|
IgnorePointer(
|
||||||
child: Center(
|
child: Center(
|
||||||
@@ -117,23 +113,38 @@ class ServiceManagementTile extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return ListTile(
|
return Card(
|
||||||
leading: Icon(
|
margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||||
icon,
|
shape: RoundedRectangleBorder(
|
||||||
color: disabled ? theme.disabledColor : null,
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
),
|
),
|
||||||
title: Text(
|
elevation: 4, // Add some elevation for better visual separation
|
||||||
label,
|
child: InkWell(
|
||||||
style: TextStyle(
|
onTap: disabled ? null : onTap, // Disable InkWell if the tile is disabled
|
||||||
color: disabled ? theme.disabledColor : null,
|
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",
|
"postOffice": "Post Office",
|
||||||
"rbiCode1": "RBI Code 1",
|
"rbiCode1": "RBI Code 1",
|
||||||
"rbiCode2": "RBI Code 2",
|
"rbiCode2": "RBI Code 2",
|
||||||
"latitude": "Latitude"
|
"latitude": "Latitude",
|
||||||
|
"address": "Customer Address",
|
||||||
|
"transactions": "Transactions"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -405,5 +405,7 @@
|
|||||||
"postOffice": "डाकघर",
|
"postOffice": "डाकघर",
|
||||||
"rbiCode1": "आरबीआई कोड 1",
|
"rbiCode1": "आरबीआई कोड 1",
|
||||||
"rbiCode2": "आरबीआई कोड 2",
|
"rbiCode2": "आरबीआई कोड 2",
|
||||||
"latitude": "अक्षांश"
|
"latitude": "अक्षांश",
|
||||||
|
"address": "ग्राहक का पता",
|
||||||
|
"transactions": "लेनदेन"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ void main() async {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Check for device compromise
|
// Check for device compromise
|
||||||
final compromisedMessage = await SecurityService.deviceCompromisedMessage;
|
//final compromisedMessage = await SecurityService.deviceCompromisedMessage;
|
||||||
if (compromisedMessage != null) {
|
// if (compromisedMessage != null) {
|
||||||
runApp(MaterialApp(
|
// runApp(MaterialApp(
|
||||||
home: SecurityErrorScreen(message: compromisedMessage),
|
// home: SecurityErrorScreen(message: compromisedMessage),
|
||||||
));
|
// ));
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
await setupDependencies();
|
await setupDependencies();
|
||||||
runApp(const KMobile());
|
runApp(const KMobile());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user