8 Commits

Author SHA1 Message Date
adb9a5330b Account Info card UI changed 2025-11-24 15:50:01 +05:30
0075abc906 Bottom navigation changed 2025-11-24 13:26:33 +05:30
353ec63916 3 changes 2025-11-24 12:56:06 +05:30
71b52cfb43 dashboard Screen UI changed 2025-11-24 12:20:35 +05:30
c1df43e9b6 dashboard#1 2025-11-20 17:45:29 +05:30
f0d5233afc Beneficiary lists changed 2025-11-20 13:31:42 +05:30
4fe6af4098 dark Theme and 18 other changes done 2025-11-20 12:33:30 +05:30
fda5d075ff Profile Screen 2025-11-18 11:33:22 +05:30
21 changed files with 1274 additions and 734 deletions

View File

@@ -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 {

View File

@@ -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);
}, },
), ),
), ),

View File

@@ -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(

View File

@@ -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,15 +89,17 @@ 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(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: ListTile(
leading: CircleAvatar( leading: CircleAvatar(
radius: 24, radius: 24,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
@@ -97,6 +125,7 @@ class _ManageBeneficiariesScreen extends State<ManageBeneficiariesScreen> {
), ),
); );
}, },
),
); );
}, },
); );
@@ -111,7 +140,27 @@ class _ManageBeneficiariesScreen extends State<ManageBeneficiariesScreen> {
), ),
body: Stack( body: Stack(
children: [ children: [
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(), _isLoading ? _buildShimmerList() : _buildBeneficiaryList(),
),
],
),
IgnorePointer( IgnorePointer(
child: Center( child: Center(
child: Opacity( child: Opacity(

View File

@@ -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(

View File

@@ -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(

View File

@@ -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(

View File

@@ -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(
"${getFullAccountType(currAccount.accountType)}: ",
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
DropdownButton<int>(
value: selectedAccountIndex,
dropdownColor: theme.colorScheme.primary,
underline: const SizedBox(),
icon: const Icon(Icons.keyboard_arrow_down),
iconEnabledColor: theme.colorScheme.onPrimary,
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: 18,
),
items: List.generate(users.length, (index) {
return DropdownMenuItem<int>(
value: index,
child: Text(
users[index].accountNo ?? 'N/A',
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
);
}),
onChanged: (int? newIndex) async {
if (newIndex == null ||
newIndex == selectedAccountIndex) {
return;
} }
if (isBalanceLoading) return;
if (isVisible) {
setState(() {
isBalanceLoading = true;
selectedAccountIndex = newIndex;
});
await Future.delayed(
const Duration(milliseconds: 200),
);
setState(() {
isBalanceLoading = false;
});
} else {
setState(() { setState(() {
selectedAccountIndex = newIndex; selectedAccountIndex = newIndex;
}); });
}
await _loadTransactions( await _loadTransactions(
users[newIndex].accountNo!, users[newIndex].accountNo!,
); );
}, },
), itemBuilder: (context, index) {
const Spacer(), final user = users[index];
IconButton( final isSelected = index == selectedAccountIndex;
icon: isRefreshing return _buildAccountCard(user, isSelected);
? 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(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
child: InkWell(
onTap: disable ? null : onTap, onTap: disable ? null : onTap,
borderRadius: BorderRadius.circular(12.0),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon( Icon(
icon, icon,
size: 30, size: 30,
color: disable color: disable
? theme.colorScheme.onSurface.withOpacity(0.3) ? theme.disabledColor
: theme.colorScheme.primary, : theme.colorScheme.primary,
grade: 200,
weight: 700,
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
label, label,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle(fontSize: 13), style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
fontSize: 12,
color: disable
? theme.disabledColor
: theme.colorScheme.onSurface,
),
), ),
], ],
), ),
),
); );
} }
} }

View File

@@ -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,7 +120,9 @@ 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(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: Opacity(
opacity: isCoolingDown ? 0.5 : 1.0, opacity: isCoolingDown ? 0.5 : 1.0,
child: ListTile( child: ListTile(
// REMOVED the 'enabled' property from here. // REMOVED the 'enabled' property from here.
@@ -149,6 +177,7 @@ class _FundTransferBeneficiaryScreenState
} }
}, },
), ),
),
); );
}, },
); );
@@ -162,7 +191,27 @@ class _FundTransferBeneficiaryScreenState
), ),
body: Stack( body: Stack(
children: [ children: [
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(), _isLoading ? _buildShimmerList() : _buildBeneficiaryList(),
),
],
),
IgnorePointer( IgnorePointer(
child: Center( child: Center(
child: Opacity( child: Opacity(

View File

@@ -34,18 +34,21 @@ class FundTransferScreen extends StatelessWidget {
builder: (context, state) { builder: (context, state) {
return Stack( return Stack(
children: [ children: [
ListView( Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
FundTransferManagementTile( Expanded(
child: FundTransferManagementTile(
icon: Symbols.person, icon: Symbols.person,
// Restore localization for the label
label: "Self Pay", label: "Self Pay",
onTap: () { onTap: () {
// The accounts list is passed directly from the constructor
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => FundTransferSelfAccountsScreen( builder: (context) =>
FundTransferSelfAccountsScreen(
debitAccountNo: creditAccountNo, debitAccountNo: creditAccountNo,
remitterName: remitterName, remitterName: remitterName,
accounts: accounts, accounts: accounts,
@@ -53,19 +56,20 @@ class FundTransferScreen extends StatelessWidget {
), ),
); );
}, },
// Disable the tile if the state is not Authenticated
disable: state is! Authenticated, disable: state is! Authenticated,
), ),
const Divider(height: 1), ),
FundTransferManagementTile( const SizedBox(height: 16),
Expanded(
child: FundTransferManagementTile(
icon: Symbols.input_circle, icon: Symbols.input_circle,
// Restore localization for the label
label: AppLocalizations.of(context).ownBank, label: AppLocalizations.of(context).ownBank,
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => FundTransferBeneficiaryScreen( builder: (context) =>
FundTransferBeneficiaryScreen(
creditAccountNo: creditAccountNo, creditAccountNo: creditAccountNo,
remitterName: remitterName, remitterName: remitterName,
isOwnBank: true, isOwnBank: true,
@@ -74,16 +78,18 @@ class FundTransferScreen extends StatelessWidget {
); );
}, },
), ),
const Divider(height: 1), ),
FundTransferManagementTile( const SizedBox(height: 16),
Expanded(
child: FundTransferManagementTile(
icon: Symbols.output_circle, icon: Symbols.output_circle,
// Restore localization for the label
label: AppLocalizations.of(context).outsideBank, label: AppLocalizations.of(context).outsideBank,
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => FundTransferBeneficiaryScreen( builder: (context) =>
FundTransferBeneficiaryScreen(
creditAccountNo: creditAccountNo, creditAccountNo: creditAccountNo,
remitterName: remitterName, remitterName: remitterName,
isOwnBank: false, isOwnBank: false,
@@ -92,9 +98,10 @@ class FundTransferScreen extends StatelessWidget {
); );
}, },
), ),
const Divider(height: 1), ),
], ],
), ),
),
IgnorePointer( IgnorePointer(
child: Center( child: Center(
child: Opacity( child: Opacity(
@@ -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,
),
),
],
),
),
),
),
); );
} }
} }

View File

@@ -55,7 +55,10 @@ 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(
margin: const EdgeInsets.symmetric(
horizontal: 12, vertical: 6),
child: ListTile(
leading: CircleAvatar( leading: CircleAvatar(
radius: 24, radius: 24,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
@@ -80,7 +83,8 @@ class FundTransferSelfAccountsScreen extends StatelessWidget {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => FundTransferSelfAmountScreen( builder: (context) =>
FundTransferSelfAmountScreen(
debitAccountNo: debitAccountNo, debitAccountNo: debitAccountNo,
creditAccount: account, // Pass the User object creditAccount: account, // Pass the User object
remitterName: remitterName, remitterName: remitterName,
@@ -88,6 +92,7 @@ class FundTransferSelfAccountsScreen extends StatelessWidget {
), ),
); );
}, },
),
); );
}, },
), ),

View File

@@ -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,34 +159,257 @@ class _ProfileScreenState extends State<ProfileScreen> {
} }
} }
// @override
// Widget build(BuildContext context) {
// final loc = AppLocalizations.of(context);
// return Scaffold(
// appBar: AppBar(
// title: Text(loc.profile), // Localized "Profile"
// ),
// body: Stack(
// children: [
// ListView(
// children: [
// ListTile(
// leading: const Icon(Icons.settings),
// title: Text(loc.preferences),
// trailing: const Icon(Icons.chevron_right),
// onTap: () {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => const PreferenceScreen()),
// );
// },
// ),
// ListTile(
// leading: const Icon(Icons.security),
// title: Text(loc.securitySettings),
// trailing: const Icon(Icons.chevron_right),
// onTap: () {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => SecuritySettingsScreen(
// 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context); final loc = AppLocalizations.of(context);
final theme = Theme.of(context);
final isDarkMode = theme.brightness == Brightness.dark;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(loc.profile), // Localized "Profile" title: Text(loc.profile),
elevation: 0,
), ),
body: Stack( body: Stack(
children: [ children: [
ListView( ListView(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
children: [ children: [
ListTile( // ===== Profile Header =====
leading: const Icon(Icons.settings), Card(
title: Text(loc.preferences), child: Padding(
trailing: const Icon(Icons.chevron_right), padding: const EdgeInsets.all(16.0),
child: Row(
children: [
// Avatar
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: theme.colorScheme.surface,
image: const DecorationImage(
image: AssetImage('assets/images/logo.png'),
fit: BoxFit.cover,
),
),
),
const SizedBox(width: 12),
// Name + mobile
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
// If you want to show the user's name instead, replace below.
widget.customerName,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 4),
Text(
widget.customerNo,
style: theme.textTheme.bodyMedium?.copyWith(
color:
theme.colorScheme.onSurface.withOpacity(0.7),
),
),
],
),
),
// Edit/Profile button (optional)
TextButton.icon(
onPressed: () {
// TODO: Navigate to edit profile if required
},
icon: const Icon(Icons.edit, size: 18),
label: const Text("Edit"),
style: TextButton.styleFrom(
foregroundColor: theme.colorScheme.onSurface,
),
),
],
),
),
),
const SizedBox(height: 16),
// ===== Section: Settings =====
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
"Settings",
style: theme.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
letterSpacing: 0.2,
),
),
),
const SizedBox(height: 8),
_SectionTile(
leadingIcon: Icons.settings,
title: loc.preferences,
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const PreferenceScreen()), builder: (context) => const PreferenceScreen(),
),
); );
}, },
), ),
ListTile( _SectionTile(
leading: const Icon(Icons.security), leadingIcon: Icons.security,
title: Text(loc.securitySettings), title: loc.securitySettings,
trailing: const Icon(Icons.chevron_right),
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
@@ -196,66 +421,113 @@ class _ProfileScreenState extends State<ProfileScreen> {
); );
}, },
), ),
ListTile( _SectionTile(
leading: const Icon(Icons.currency_rupee), leadingIcon: Icons.currency_rupee,
title: Text(AppLocalizations.of(context).dailylimit), title: loc.dailylimit,
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const DailyLimitScreen()), builder: (context) => const DailyLimitScreen(),
),
); );
}, },
), ),
SwitchListTile( Card(
title: child: SwitchListTile(
Text(AppLocalizations.of(context).enableFingerprintLogin), 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),
const SizedBox(height: 16),
const Divider(height: 24),
// ===== Section: Actions =====
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
"Exit",
style: theme.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
letterSpacing: 0.2,
),
),
),
const SizedBox(height: 8),
_SectionTile(
leadingIcon: Icons.exit_to_app,
title: loc.logout,
trailChevron: false, // action tile, no chevron
onTap: () async { onTap: () async {
final shouldExit = await showDialog<bool>( final shouldExit = await showDialog<bool>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: Text(AppLocalizations.of(context).logout), title: Text(loc.logout),
content: Text(AppLocalizations.of(context).logoutCheck), content: Text(loc.logoutCheck),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(false), onPressed: () => Navigator.of(context).pop(false),
child: Text(AppLocalizations.of(context).no), child: Text(loc.no),
), ),
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(true), onPressed: () => Navigator.of(context).pop(true),
child: Text(AppLocalizations.of(context).yes), child: Text(loc.yes),
), ),
], ],
), ),
@@ -269,9 +541,10 @@ class _ProfileScreenState extends State<ProfileScreen> {
} }
}, },
), ),
ListTile( _SectionTile(
leading: const Icon(Icons.logout), leadingIcon: Icons.logout,
title: Text(AppLocalizations.of(context).deregister), title: loc.deregister,
trailChevron: false,
onTap: () async { onTap: () async {
final shouldLogout = await showDialog<bool>( final shouldLogout = await showDialog<bool>(
context: context, context: context,
@@ -283,17 +556,24 @@ class _ProfileScreenState extends State<ProfileScreen> {
} }
}, },
), ),
const SizedBox(height: 24),
], ],
), ),
// ===== Watermark (kept subtle, no theme change) =====
IgnorePointer( 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,
),
), ),
), ),
), ),
@@ -304,3 +584,39 @@ class _ProfileScreenState extends State<ProfileScreen> {
); );
} }
} }
class _SectionTile extends StatelessWidget {
const _SectionTile({
required this.leadingIcon,
required this.title,
this.onTap,
this.trailChevron = true,
});
final IconData leadingIcon;
final String title;
final VoidCallback? onTap;
final bool trailChevron;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Card(
margin: const EdgeInsets.only(bottom: 10),
child: ListTile(
leading: Icon(leadingIcon, color: theme.colorScheme.onSurface),
title: Text(title, style: theme.textTheme.bodyLarge),
trailing: trailChevron
? Icon(Icons.chevron_right, color: theme.colorScheme.onSurface)
: null,
onTap: onTap,
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
);
}
}

View File

@@ -40,7 +40,7 @@ class SecuritySettingsScreen extends StatelessWidget {
); );
}, },
), ),
const Divider(height: 1), Divider(height: 1, color: Theme.of(context).dividerColor),
ListTile( 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'),

View File

@@ -23,9 +23,13 @@ class _QuickPayScreen extends State<QuickPayScreen> {
), ),
body: Stack( body: Stack(
children: [ children: [
ListView( Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
QuickPayManagementTile( Expanded(
child: QuickPayManagementTile(
icon: Symbols.input_circle, icon: Symbols.input_circle,
label: AppLocalizations.of(context).ownBank, label: AppLocalizations.of(context).ownBank,
onTap: () { onTap: () {
@@ -39,8 +43,10 @@ class _QuickPayScreen extends State<QuickPayScreen> {
); );
}, },
), ),
const Divider(height: 1), ),
QuickPayManagementTile( const SizedBox(height: 16),
Expanded(
child: QuickPayManagementTile(
icon: Symbols.output_circle, icon: Symbols.output_circle,
label: AppLocalizations.of(context).outsideBank, label: AppLocalizations.of(context).outsideBank,
onTap: () { onTap: () {
@@ -54,9 +60,10 @@ class _QuickPayScreen extends State<QuickPayScreen> {
); );
}, },
), ),
const Divider(height: 1), ),
], ],
), ),
),
IgnorePointer( IgnorePointer(
child: Center( child: Center(
child: Opacity( child: Opacity(
@@ -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,
),
),
],
),
),
),
); );
} }
} }

View File

@@ -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,7 +58,8 @@
); );
} }
Widget _buildDetailRow(String label, String value) { Widget _buildDetailRow(BuildContext context, String label, String value) {
final theme = Theme.of(context);
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0), padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Column( child: Column(
@@ -68,7 +69,7 @@
label, label,
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: Colors.grey[600], color: theme.textTheme.bodySmall?.color,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@@ -77,10 +78,10 @@
onTap: () => _launchUrl('tel:$value'), onTap: () => _launchUrl('tel:$value'),
child: Text( child: Text(
value, value,
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Colors.blue, // Indicate it's clickable color: theme.colorScheme.primary, // Indicate it's clickable
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
), ),
), ),
@@ -92,12 +93,11 @@
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
const Divider(height: 16), Divider(height: 16, color: theme.dividerColor),
], ],
), ),
); );
} }
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)) {

View File

@@ -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: [

View File

@@ -27,23 +27,13 @@ class _ServiceScreen extends State<ServiceScreen> {
), ),
body: Stack( body: Stack(
children: [ children: [
ListView( Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
// ServiceManagementTile( Expanded(
// icon: Symbols.add, child: ServiceManagementTile(
// label: AppLocalizations.of(context).accountOpeningDeposit,
// onTap: () {},
// disabled: true,
// ),
// const Divider(height: 1),
// ServiceManagementTile(
// icon: Symbols.add,
// label: AppLocalizations.of(context).accountOpeningLoan,
// onTap: () {},
// disabled: true,
// ),
// const Divider(height: 1),
ServiceManagementTile(
icon: Symbols.captive_portal, icon: Symbols.captive_portal,
label: AppLocalizations.of(context).quickLinks, label: AppLocalizations.of(context).quickLinks,
onTap: () { onTap: () {
@@ -54,8 +44,10 @@ class _ServiceScreen extends State<ServiceScreen> {
}, },
disabled: false, disabled: false,
), ),
const Divider(height: 1), ),
ServiceManagementTile( const SizedBox(height: 16),
Expanded(
child: ServiceManagementTile(
icon: Symbols.question_mark, icon: Symbols.question_mark,
label: AppLocalizations.of(context).faq, label: AppLocalizations.of(context).faq,
onTap: () { onTap: () {
@@ -65,8 +57,10 @@ class _ServiceScreen extends State<ServiceScreen> {
}, },
disabled: false, disabled: false,
), ),
const Divider(height: 1), ),
ServiceManagementTile( const SizedBox(height: 16),
Expanded(
child: ServiceManagementTile(
icon: Symbols.location_pin, icon: Symbols.location_pin,
label: "ATM Locator", label: "ATM Locator",
onTap: () { onTap: () {
@@ -77,9 +71,11 @@ class _ServiceScreen extends State<ServiceScreen> {
}, },
disabled: false, disabled: false,
), ),
const Divider(height: 1), ),
// No Spacer() needed here as Expanded children will fill space
], ],
), ),
),
IgnorePointer( IgnorePointer(
child: Center( child: Center(
child: Opacity( child: Opacity(
@@ -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),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
elevation: 4, // Add some elevation for better visual separation
child: InkWell(
onTap: disabled ? null : onTap, // Disable InkWell if the tile is disabled
borderRadius: BorderRadius.circular(12.0),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon, icon,
color: disabled ? theme.disabledColor : null, size: 48, // Make icon larger
color: disabled ? theme.disabledColor : theme.colorScheme.primary,
), ),
title: Text( const SizedBox(height: 12),
Text(
label, label,
style: TextStyle( textAlign: TextAlign.center,
color: disabled ? theme.disabledColor : null, style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: disabled ? theme.disabledColor : theme.colorScheme.onSurface,
),
),
],
), ),
), ),
trailing: Icon(
Symbols.arrow_right,
size: 20,
color: disabled ? theme.disabledColor : null,
), ),
onTap: disabled ? null : onTap,
); );
} }
} }

View File

@@ -404,5 +404,7 @@
"postOffice": "Post Office", "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"
} }

View File

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

View File

@@ -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());
} }