From 8cfca113bf3721ee5c2ff432c01708669fdc6c6b Mon Sep 17 00:00:00 2001 From: asif Date: Mon, 10 Nov 2025 16:50:29 +0530 Subject: [PATCH] dart format --- lib/api/services/auth_service.dart | 12 +- lib/api/services/limit_service.dart | 4 +- lib/config/routes.dart | 5 +- lib/data/models/beneficiary.dart | 4 +- lib/data/repositories/auth_repository.dart | 26 +- lib/di/injection.dart | 6 +- lib/features/auth/controllers/auth_cubit.dart | 94 ++- lib/features/auth/controllers/auth_state.dart | 14 +- lib/features/auth/models/auth_token.dart | 33 +- lib/features/auth/screens/login_screen.dart | 391 +++++------ lib/features/auth/screens/mpin_screen.dart | 18 +- .../auth/screens/tnc_required_screen.dart | 73 +- .../dashboard/screens/dashboard_screen.dart | 63 +- .../fund_transfer/screens/cooldown.dart | 159 ++--- .../screens/fund_transfer_amount_screen.dart | 127 ++-- .../fund_transfer_beneficiary_screen.dart | 4 +- .../screens/fund_transfer_screen.dart | 240 +++---- .../fund_transfer_self_accounts_screen.dart | 177 +++-- .../fund_transfer_self_amount_screen.dart | 473 +++++++------ .../profile/daily_transaction_limit.dart | 359 +++++----- lib/features/profile/profile_screen.dart | 285 ++++---- .../profile/tpin/change_tpin_otp_screen.dart | 230 +++--- .../profile/tpin/change_tpin_screen.dart | 5 +- .../quick_pay_outside_bank_screen.dart | 279 ++++---- .../screens/quick_pay_within_bank_screen.dart | 661 +++++++++--------- lib/main.dart | 14 +- lib/widgets/pin_input_field.dart | 2 +- lib/widgets/tnc_dialog.dart | 210 +++--- 28 files changed, 1995 insertions(+), 1973 deletions(-) diff --git a/lib/api/services/auth_service.dart b/lib/api/services/auth_service.dart index 5bbe894..234070e 100644 --- a/lib/api/services/auth_service.dart +++ b/lib/api/services/auth_service.dart @@ -142,24 +142,22 @@ class AuthService { return; } - Future setTncflag() async{ + Future setTncflag() async { try { final response = await _dio.post( '/api/auth/tnc', data: {"flag": 'Y'}, ); - if (response.statusCode != 200) { + if (response.statusCode != 200) { throw AuthException('Failed to proceed with T&C'); } - } - on DioException catch (e) { + } on DioException catch (e) { if (kDebugMode) { print(e.toString()); } throw NetworkException('Network error during T&C Setup'); } catch (e) { - throw UnexpectedException( - 'Unexpected error: ${e.toString()}'); + throw UnexpectedException('Unexpected error: ${e.toString()}'); } } -} \ No newline at end of file +} diff --git a/lib/api/services/limit_service.dart b/lib/api/services/limit_service.dart index 5ad3b6f..4ace046 100644 --- a/lib/api/services/limit_service.dart +++ b/lib/api/services/limit_service.dart @@ -39,10 +39,10 @@ class LimitService { } } - void editLimit( double newLimit) async { + void editLimit(double newLimit) async { try { final response = await _dio.post('/api/customer/daily-limit', - data: '{"amount": $newLimit}'); + data: '{"amount": $newLimit}'); if (response.statusCode == 200) { log('Response: ${response.data}'); } else { diff --git a/lib/config/routes.dart b/lib/config/routes.dart index a9e16b5..27474f7 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -10,7 +10,7 @@ import '../features/dashboard/screens/dashboard_screen.dart'; // import '../features/transactions/screens/transactions_screen.dart'; // import '../features/payments/screens/payments_screen.dart'; // import '../features/settings/screens/settings_screen.dart'; - import 'package:kmobile/features/auth/screens/tnc_required_screen.dart'; +import 'package:kmobile/features/auth/screens/tnc_required_screen.dart'; class AppRoutes { // Private constructor to prevent instantiation @@ -36,7 +36,8 @@ class AppRoutes { case login: return MaterialPageRoute(builder: (_) => const LoginScreen()); case TncRequiredScreen.routeName: // Renamed class - return MaterialPageRoute(builder: (_) => const TncRequiredScreen()); // Renamed class + return MaterialPageRoute( + builder: (_) => const TncRequiredScreen()); // Renamed class case mPin: return MaterialPageRoute( builder: (_) => const MPinScreen( diff --git a/lib/data/models/beneficiary.dart b/lib/data/models/beneficiary.dart index 425e03f..5ba2e12 100644 --- a/lib/data/models/beneficiary.dart +++ b/lib/data/models/beneficiary.dart @@ -25,7 +25,9 @@ class Beneficiary { return Beneficiary( accountNo: json['account_no'] ?? json['accountNo'] ?? '', accountType: json['account_type'] ?? json['accountType'] ?? '', - createdAt: json['createdAt'] == null ? null : DateTime.tryParse(json['createdAt']), + createdAt: json['createdAt'] == null + ? null + : DateTime.tryParse(json['createdAt']), name: json['name'] ?? '', ifscCode: json['ifsc_code'] ?? json['ifscCode'] ?? '', bankName: json['bank_name'] ?? json['bankName'] ?? '', diff --git a/lib/data/repositories/auth_repository.dart b/lib/data/repositories/auth_repository.dart index 860d92b..62c688a 100644 --- a/lib/data/repositories/auth_repository.dart +++ b/lib/data/repositories/auth_repository.dart @@ -17,7 +17,8 @@ class AuthRepository { AuthRepository(this._authService, this._userService, this._secureStorage); - Future<(List, AuthToken)> login(String customerNo, String password) async { + Future<(List, AuthToken)> login( + String customerNo, String password) async { // Create credentials and call service final credentials = AuthCredentials(customerNo: customerNo, password: password); @@ -64,21 +65,22 @@ class AuthRepository { final authToken = AuthToken( accessToken: accessToken, expiresAt: DateTime.parse(expiryString), - tnc: tncString == 'true', // Parse 'true' string to true, otherwise false + tnc: + tncString == 'true', // Parse 'true' string to true, otherwise false ); return authToken; } return null; } - Future acceptTnc() async { - // This method calls the setTncFlag function - try { - await _authService.setTncflag(); - } catch (e) { - // Handle or rethrow the error as needed - print('Error setting TNC flag: $e'); - rethrow; - } - } + Future acceptTnc() async { + // This method calls the setTncFlag function + try { + await _authService.setTncflag(); + } catch (e) { + // Handle or rethrow the error as needed + print('Error setting TNC flag: $e'); + rethrow; + } + } } diff --git a/lib/di/injection.dart b/lib/di/injection.dart index ca5016e..63dffe9 100644 --- a/lib/di/injection.dart +++ b/lib/di/injection.dart @@ -61,15 +61,15 @@ Future setupDependencies() async { ); // Register controllers/cubits - getIt.registerFactory( - () => AuthCubit(getIt(), getIt(), getIt())); + getIt.registerFactory(() => AuthCubit( + getIt(), getIt(), getIt())); } Dio _createDioClient() { final dio = Dio( BaseOptions( baseUrl: - 'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com:8080', //test + 'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com:8080', //test //'http://lb-kccb-mobile-banking-app-848675342.ap-south-1.elb.amazonaws.com', //prod //'https://kccbmbnk.net', //prod small connectTimeout: const Duration(seconds: 60), diff --git a/lib/features/auth/controllers/auth_cubit.dart b/lib/features/auth/controllers/auth_cubit.dart index e7eec5d..2a0040a 100644 --- a/lib/features/auth/controllers/auth_cubit.dart +++ b/lib/features/auth/controllers/auth_cubit.dart @@ -42,57 +42,55 @@ class AuthCubit extends Cubit { } } - Future login(String customerNo, String password) async { - emit(AuthLoading()); - try { - final (users, authToken) = await _authRepository.login(customerNo, password); + Future login(String customerNo, String password) async { + emit(AuthLoading()); + try { + final (users, authToken) = + await _authRepository.login(customerNo, password); - if (authToken.tnc == false) { - emit(ShowTncDialog(authToken, users)); - } else { - await _checkMpinAndNavigate(users); - } - } catch (e) { - emit(AuthError(e is AuthException ? e.message : e.toString())); - } - } - + if (authToken.tnc == false) { + emit(ShowTncDialog(authToken, users)); + } else { + await _checkMpinAndNavigate(users); + } + } catch (e) { + emit(AuthError(e is AuthException ? e.message : e.toString())); + } + } - Future onTncDialogResult( - bool agreed, AuthToken authToken, List users) async { - if (agreed) { - try { - await _authRepository.acceptTnc(); - // The user is NOT fully authenticated yet. Just check for MPIN. - await _checkMpinAndNavigate(users); - } catch (e) { - emit(AuthError('Failed to accept TNC: $e')); - } - } else { - emit(NavigateToTncRequiredScreen()); - } - } + Future onTncDialogResult( + bool agreed, AuthToken authToken, List users) async { + if (agreed) { + try { + await _authRepository.acceptTnc(); + // The user is NOT fully authenticated yet. Just check for MPIN. + await _checkMpinAndNavigate(users); + } catch (e) { + emit(AuthError('Failed to accept TNC: $e')); + } + } else { + emit(NavigateToTncRequiredScreen()); + } + } void mpinSetupCompleted() { - if (state is NavigateToMpinSetupScreen) { - final users = (state as NavigateToMpinSetupScreen).users; - emit(Authenticated(users)); - } else { - // Handle unexpected state if necessary - emit(AuthError("Invalid state during MPIN setup completion.")); - } - } - - - Future _checkMpinAndNavigate(List users) async { - final mpin = await _secureStorage.read('mpin'); - if (mpin == null) { - // No MPIN, tell UI to navigate to MPIN setup, carrying user data - emit(NavigateToMpinSetupScreen(users)); - } else { - // MPIN exists, user is authenticated - emit(Authenticated(users)); - } - } + if (state is NavigateToMpinSetupScreen) { + final users = (state as NavigateToMpinSetupScreen).users; + emit(Authenticated(users)); + } else { + // Handle unexpected state if necessary + emit(AuthError("Invalid state during MPIN setup completion.")); + } + } + Future _checkMpinAndNavigate(List users) async { + final mpin = await _secureStorage.read('mpin'); + if (mpin == null) { + // No MPIN, tell UI to navigate to MPIN setup, carrying user data + emit(NavigateToMpinSetupScreen(users)); + } else { + // MPIN exists, user is authenticated + emit(Authenticated(users)); + } + } } diff --git a/lib/features/auth/controllers/auth_state.dart b/lib/features/auth/controllers/auth_state.dart index 768eef9..331d321 100644 --- a/lib/features/auth/controllers/auth_state.dart +++ b/lib/features/auth/controllers/auth_state.dart @@ -46,13 +46,13 @@ class ShowTncDialog extends AuthState { // States to trigger specific navigations from the UI class NavigateToTncRequiredScreen extends AuthState {} - class NavigateToMpinSetupScreen extends AuthState { - final List users; +class NavigateToMpinSetupScreen extends AuthState { + final List users; - const NavigateToMpinSetupScreen(this.users); + const NavigateToMpinSetupScreen(this.users); - @override - List get props => [users]; - } + @override + List get props => [users]; +} -class NavigateToDashboardScreen extends AuthState {} \ No newline at end of file +class NavigateToDashboardScreen extends AuthState {} diff --git a/lib/features/auth/models/auth_token.dart b/lib/features/auth/models/auth_token.dart index 18612c6..6011022 100644 --- a/lib/features/auth/models/auth_token.dart +++ b/lib/features/auth/models/auth_token.dart @@ -14,24 +14,25 @@ class AuthToken extends Equatable { required this.tnc, }); - factory AuthToken.fromJson(Map json) { - final token = json['token']; + factory AuthToken.fromJson(Map json) { + final token = json['token']; - // Safely extract tnc.mobile directly from the outer JSON - bool tncMobileValue = false; // Default to false if not found or invalid - if (json.containsKey('tnc') && json['tnc'] is Map) { - final tncMap = json['tnc'] as Map; - if (tncMap.containsKey('mobile') && tncMap['mobile'] is bool) { - tncMobileValue = tncMap['mobile'] as bool; + // Safely extract tnc.mobile directly from the outer JSON + bool tncMobileValue = false; // Default to false if not found or invalid + if (json.containsKey('tnc') && json['tnc'] is Map) { + final tncMap = json['tnc'] as Map; + if (tncMap.containsKey('mobile') && tncMap['mobile'] is bool) { + tncMobileValue = tncMap['mobile'] as bool; + } } - } - return AuthToken( - accessToken: token, - expiresAt: _decodeExpiryFromToken(token), // This method is still valid for JWT expiry - tnc: tncMobileValue, // Use the correctly extracted value - ); -} + return AuthToken( + accessToken: token, + expiresAt: _decodeExpiryFromToken( + token), // This method is still valid for JWT expiry + tnc: tncMobileValue, // Use the correctly extracted value + ); + } static DateTime _decodeExpiryFromToken(String token) { try { @@ -55,7 +56,7 @@ class AuthToken extends Equatable { return DateTime.now().add(const Duration(hours: 1)); } } - + // static bool _decodeTncFromToken(String token) { // try { // final parts = token.split('.'); diff --git a/lib/features/auth/screens/login_screen.dart b/lib/features/auth/screens/login_screen.dart index 92753da..557748a 100644 --- a/lib/features/auth/screens/login_screen.dart +++ b/lib/features/auth/screens/login_screen.dart @@ -41,201 +41,201 @@ class LoginScreenState extends State @override Widget build(BuildContext context) { - // return Scaffold( - // body: BlocConsumer( - // listener: (context, state) async { - // if (state is ShowTncDialog) { - // // The dialog now returns a boolean for the 'disagree' case, - // // or it completes when the 'proceed' action is finished. - // final agreed = await showDialog( - // context: context, - // barrierDismissible: false, - // builder: (dialogContext) => TncDialog( - // onProceed: () async { - // // This function is passed to the dialog. - // // It calls the cubit and completes when the cubit's work is done. - // await context - // .read() - // .onTncDialogResult(true, state.authToken, state.users); - // }, - // ), - // ); + // return Scaffold( + // body: BlocConsumer( + // listener: (context, state) async { + // if (state is ShowTncDialog) { + // // The dialog now returns a boolean for the 'disagree' case, + // // or it completes when the 'proceed' action is finished. + // final agreed = await showDialog( + // context: context, + // barrierDismissible: false, + // builder: (dialogContext) => TncDialog( + // onProceed: () async { + // // This function is passed to the dialog. + // // It calls the cubit and completes when the cubit's work is done. + // await context + // .read() + // .onTncDialogResult(true, state.authToken, state.users); + // }, + // ), + // ); - // // If 'agreed' is false, it means the user clicked 'Disagree'. - // if (agreed == false) { - // if (!context.mounted) return; - // context - // .read() - // .onTncDialogResult(false, state.authToken, state.users); - // } - // } else if (state is NavigateToTncRequiredScreen) { - // Navigator.of(context).pushNamed(TncRequiredScreen.routeName); - // } else if (state is NavigateToMpinSetupScreen) { - // Navigator.of(context).push( // Use push, NOT pushReplacement - // MaterialPageRoute( - // builder: (_) => MPinScreen( - // mode: MPinMode.set, - // onCompleted: (_) { - // // This clears the entire stack and pushes the dashboard - // Navigator.of(context, rootNavigator: true).pushAndRemoveUntil( - // MaterialPageRoute(builder: (_) => const NavigationScaffold()), - // (route) => false, - // ); - // }, - // ), - // ), - // ); - // } else if (state is NavigateToDashboardScreen) { - // Navigator.of(context).pushReplacement( - // MaterialPageRoute(builder: (_) => const NavigationScaffold()), - // ); - // } else if (state is AuthError) { - // if (state.message == 'MIGRATED_USER_HAS_NO_PASSWORD') { - // Navigator.of(context).push(MaterialPageRoute( - // builder: (_) => SetPasswordScreen( - // customerNo: _customerNumberController.text.trim(), - // ))); - // } else { - // ScaffoldMessenger.of(context) - // .showSnackBar(SnackBar(content: Text(state.message))); - // } - // } - // }, - // builder: (context, state) { - // // The commented out section is removed for clarity, the logic is now above. - // return Padding( - // padding: const EdgeInsets.all(24.0), - // child: Form( - // key: _formKey, - // child: Column( - // mainAxisAlignment: MainAxisAlignment.center, - // children: [ - // Image.asset( - // 'assets/images/logo.png', - // width: 150, - // height: 150, - // errorBuilder: (context, error, stackTrace) { - // return Icon( - // Icons.account_balance, - // size: 100, - // color: Theme.of(context).primaryColor, - // ); - // }, - // ), - // const SizedBox(height: 16), - // Text( - // AppLocalizations.of(context).kccb, - // style: TextStyle( - // fontSize: 32, - // fontWeight: FontWeight.bold, - // color: Theme.of(context).primaryColor, - // ), - // ), - // const SizedBox(height: 48), - // TextFormField( - // controller: _customerNumberController, - // decoration: InputDecoration( - // labelText: AppLocalizations.of(context).customerNumber, - // border: const OutlineInputBorder(), - // isDense: true, - // filled: true, - // fillColor: Theme.of(context).scaffoldBackgroundColor, - // enabledBorder: OutlineInputBorder( - // borderSide: BorderSide( - // color: Theme.of(context).colorScheme.outline), - // ), - // focusedBorder: OutlineInputBorder( - // borderSide: BorderSide( - // color: Theme.of(context).colorScheme.primary, - // width: 2), - // ), - // ), - // keyboardType: TextInputType.number, - // textInputAction: TextInputAction.next, - // validator: (value) { - // if (value == null || value.isEmpty) { - // return AppLocalizations.of(context).pleaseEnterUsername; - // } - // return null; - // }, - // ), - // const SizedBox(height: 24), - // TextFormField( - // controller: _passwordController, - // obscureText: _obscurePassword, - // textInputAction: TextInputAction.done, - // onFieldSubmitted: (_) => _submitForm(), - // decoration: InputDecoration( - // labelText: AppLocalizations.of(context).password, - // border: const OutlineInputBorder(), - // isDense: true, - // filled: true, - // fillColor: Theme.of(context).scaffoldBackgroundColor, - // enabledBorder: OutlineInputBorder( - // borderSide: BorderSide( - // color: Theme.of(context).colorScheme.outline), - // ), - // focusedBorder: OutlineInputBorder( - // borderSide: BorderSide( - // color: Theme.of(context).colorScheme.primary, - // width: 2), - // ), - // suffixIcon: IconButton( - // icon: Icon( - // _obscurePassword - // ? Icons.visibility - // : Icons.visibility_off, - // ), - // onPressed: () { - // setState(() { - // _obscurePassword = !_obscurePassword; - // }); - // }, - // ), - // ), - // validator: (value) { - // if (value == null || value.isEmpty) { - // return AppLocalizations.of(context).pleaseEnterPassword; - // } - // return null; - // }, - // ), - // const SizedBox(height: 24), - // SizedBox( - // width: 250, - // child: ElevatedButton( - // onPressed: state is AuthLoading ? null : _submitForm, - // style: ElevatedButton.styleFrom( - // shape: const StadiumBorder(), - // padding: const EdgeInsets.symmetric(vertical: 16), - // backgroundColor: - // Theme.of(context).scaffoldBackgroundColor, - // foregroundColor: Theme.of(context).primaryColorDark, - // side: BorderSide( - // color: Theme.of(context).colorScheme.outline, - // width: 1), - // elevation: 0, - // ), - // child: state is AuthLoading - // ? const CircularProgressIndicator() - // : Text( - // AppLocalizations.of(context).login, - // style: TextStyle( - // color: Theme.of(context) - // .colorScheme - // .onPrimaryContainer), - // ), - // ), - // ), - // const SizedBox(height: 25), - // ], - // ), - // ), - // ); - // }, - // ), - // ); - return Scaffold( + // // If 'agreed' is false, it means the user clicked 'Disagree'. + // if (agreed == false) { + // if (!context.mounted) return; + // context + // .read() + // .onTncDialogResult(false, state.authToken, state.users); + // } + // } else if (state is NavigateToTncRequiredScreen) { + // Navigator.of(context).pushNamed(TncRequiredScreen.routeName); + // } else if (state is NavigateToMpinSetupScreen) { + // Navigator.of(context).push( // Use push, NOT pushReplacement + // MaterialPageRoute( + // builder: (_) => MPinScreen( + // mode: MPinMode.set, + // onCompleted: (_) { + // // This clears the entire stack and pushes the dashboard + // Navigator.of(context, rootNavigator: true).pushAndRemoveUntil( + // MaterialPageRoute(builder: (_) => const NavigationScaffold()), + // (route) => false, + // ); + // }, + // ), + // ), + // ); + // } else if (state is NavigateToDashboardScreen) { + // Navigator.of(context).pushReplacement( + // MaterialPageRoute(builder: (_) => const NavigationScaffold()), + // ); + // } else if (state is AuthError) { + // if (state.message == 'MIGRATED_USER_HAS_NO_PASSWORD') { + // Navigator.of(context).push(MaterialPageRoute( + // builder: (_) => SetPasswordScreen( + // customerNo: _customerNumberController.text.trim(), + // ))); + // } else { + // ScaffoldMessenger.of(context) + // .showSnackBar(SnackBar(content: Text(state.message))); + // } + // } + // }, + // builder: (context, state) { + // // The commented out section is removed for clarity, the logic is now above. + // return Padding( + // padding: const EdgeInsets.all(24.0), + // child: Form( + // key: _formKey, + // child: Column( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Image.asset( + // 'assets/images/logo.png', + // width: 150, + // height: 150, + // errorBuilder: (context, error, stackTrace) { + // return Icon( + // Icons.account_balance, + // size: 100, + // color: Theme.of(context).primaryColor, + // ); + // }, + // ), + // const SizedBox(height: 16), + // Text( + // AppLocalizations.of(context).kccb, + // style: TextStyle( + // fontSize: 32, + // fontWeight: FontWeight.bold, + // color: Theme.of(context).primaryColor, + // ), + // ), + // const SizedBox(height: 48), + // TextFormField( + // controller: _customerNumberController, + // decoration: InputDecoration( + // labelText: AppLocalizations.of(context).customerNumber, + // border: const OutlineInputBorder(), + // isDense: true, + // filled: true, + // fillColor: Theme.of(context).scaffoldBackgroundColor, + // enabledBorder: OutlineInputBorder( + // borderSide: BorderSide( + // color: Theme.of(context).colorScheme.outline), + // ), + // focusedBorder: OutlineInputBorder( + // borderSide: BorderSide( + // color: Theme.of(context).colorScheme.primary, + // width: 2), + // ), + // ), + // keyboardType: TextInputType.number, + // textInputAction: TextInputAction.next, + // validator: (value) { + // if (value == null || value.isEmpty) { + // return AppLocalizations.of(context).pleaseEnterUsername; + // } + // return null; + // }, + // ), + // const SizedBox(height: 24), + // TextFormField( + // controller: _passwordController, + // obscureText: _obscurePassword, + // textInputAction: TextInputAction.done, + // onFieldSubmitted: (_) => _submitForm(), + // decoration: InputDecoration( + // labelText: AppLocalizations.of(context).password, + // border: const OutlineInputBorder(), + // isDense: true, + // filled: true, + // fillColor: Theme.of(context).scaffoldBackgroundColor, + // enabledBorder: OutlineInputBorder( + // borderSide: BorderSide( + // color: Theme.of(context).colorScheme.outline), + // ), + // focusedBorder: OutlineInputBorder( + // borderSide: BorderSide( + // color: Theme.of(context).colorScheme.primary, + // width: 2), + // ), + // suffixIcon: IconButton( + // icon: Icon( + // _obscurePassword + // ? Icons.visibility + // : Icons.visibility_off, + // ), + // onPressed: () { + // setState(() { + // _obscurePassword = !_obscurePassword; + // }); + // }, + // ), + // ), + // validator: (value) { + // if (value == null || value.isEmpty) { + // return AppLocalizations.of(context).pleaseEnterPassword; + // } + // return null; + // }, + // ), + // const SizedBox(height: 24), + // SizedBox( + // width: 250, + // child: ElevatedButton( + // onPressed: state is AuthLoading ? null : _submitForm, + // style: ElevatedButton.styleFrom( + // shape: const StadiumBorder(), + // padding: const EdgeInsets.symmetric(vertical: 16), + // backgroundColor: + // Theme.of(context).scaffoldBackgroundColor, + // foregroundColor: Theme.of(context).primaryColorDark, + // side: BorderSide( + // color: Theme.of(context).colorScheme.outline, + // width: 1), + // elevation: 0, + // ), + // child: state is AuthLoading + // ? const CircularProgressIndicator() + // : Text( + // AppLocalizations.of(context).login, + // style: TextStyle( + // color: Theme.of(context) + // .colorScheme + // .onPrimaryContainer), + // ), + // ), + // ), + // const SizedBox(height: 25), + // ], + // ), + // ), + // ); + // }, + // ), + // ); + return Scaffold( body: BlocConsumer( listener: (context, state) { if (state is ShowTncDialog) { @@ -255,7 +255,8 @@ class LoginScreenState extends State } else if (state is NavigateToTncRequiredScreen) { Navigator.of(context).pushNamed(TncRequiredScreen.routeName); } else if (state is NavigateToMpinSetupScreen) { - Navigator.of(context).push( // Use push, NOT pushReplacement + Navigator.of(context).push( + // Use push, NOT pushReplacement MaterialPageRoute( builder: (_) => MPinScreen( mode: MPinMode.set, diff --git a/lib/features/auth/screens/mpin_screen.dart b/lib/features/auth/screens/mpin_screen.dart index f1a16af..9d1f32e 100644 --- a/lib/features/auth/screens/mpin_screen.dart +++ b/lib/features/auth/screens/mpin_screen.dart @@ -185,16 +185,16 @@ class _MPinScreenState extends State with TickerProviderStateMixin { ), ); break; - case MPinMode.confirm: - if (widget.initialPin == pin) { - // 1) persist the pin - await storage.write('mpin', pin); + case MPinMode.confirm: + if (widget.initialPin == pin) { + // 1) persist the pin + await storage.write('mpin', pin); - // 2) Call the onCompleted callback to let the parent handle navigation - if (mounted) { - widget.onCompleted?.call(pin); - } - } else { + // 2) Call the onCompleted callback to let the parent handle navigation + if (mounted) { + widget.onCompleted?.call(pin); + } + } else { setState(() { _isError = true; errorText = AppLocalizations.of(context).pinsDoNotMatch; diff --git a/lib/features/auth/screens/tnc_required_screen.dart b/lib/features/auth/screens/tnc_required_screen.dart index 23b665a..3bbd76b 100644 --- a/lib/features/auth/screens/tnc_required_screen.dart +++ b/lib/features/auth/screens/tnc_required_screen.dart @@ -1,39 +1,40 @@ - import 'package:flutter/material.dart'; +import 'package:flutter/material.dart'; - class TncRequiredScreen extends StatelessWidget { // Renamed class - const TncRequiredScreen({Key? key}) : super(key: key); +class TncRequiredScreen extends StatelessWidget { + // Renamed class + const TncRequiredScreen({Key? key}) : super(key: key); - static const routeName = '/tnc-required'; + static const routeName = '/tnc-required'; - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Terms and Conditions'), - ), - body: Center( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'You must accept the Terms and Conditions to use the application.', - textAlign: TextAlign.center, - style: TextStyle(fontSize: 18), - ), - const SizedBox(height: 20), - ElevatedButton( - onPressed: () { - // This will take the user back to the previous screen - Navigator.of(context).pop(); - }, - child: const Text('Go Back'), - ), - ], - ), - ), - ), - ); - } - } \ No newline at end of file + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Terms and Conditions'), + ), + body: Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'You must accept the Terms and Conditions to use the application.', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 18), + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: () { + // This will take the user back to the previous screen + Navigator.of(context).pop(); + }, + child: const Text('Go Back'), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/features/dashboard/screens/dashboard_screen.dart b/lib/features/dashboard/screens/dashboard_screen.dart index ce87af1..bb8bc13 100644 --- a/lib/features/dashboard/screens/dashboard_screen.dart +++ b/lib/features/dashboard/screens/dashboard_screen.dart @@ -138,7 +138,7 @@ class _DashboardScreenState extends State // Convert to title case switch (accountType.toLowerCase()) { case 'sa': - return AppLocalizations.of(context).savingsAccount; + return AppLocalizations.of(context).savingsAccount; case 'sb': return AppLocalizations.of(context).savingsAccount; case 'ln': @@ -269,7 +269,9 @@ class _DashboardScreenState extends State final users = state.users; final currAccount = users[selectedAccountIndex]; final accountType = currAccount.accountType?.toLowerCase(); - final isPaymentDisabled = accountType != 'sa' && accountType != 'sb' && accountType != 'ca'; + final isPaymentDisabled = accountType != 'sa' && + accountType != 'sb' && + accountType != 'ca'; // first‐time load if (!_txInitialized) { _txInitialized = true; @@ -484,36 +486,35 @@ class _DashboardScreenState extends State ); }, ), - _buildQuickLink( - Symbols.currency_rupee, - AppLocalizations.of(context).quickPay, - () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => QuickPayScreen( - debitAccount: currAccount.accountNo!, - ), - ), - ); - }, - disable: isPaymentDisabled, - ), + _buildQuickLink( + Symbols.currency_rupee, + AppLocalizations.of(context).quickPay, + () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => QuickPayScreen( + debitAccount: currAccount.accountNo!, + ), + ), + ); + }, + disable: isPaymentDisabled, + ), _buildQuickLink(Symbols.send_money, - AppLocalizations.of(context).fundTransfer, () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => FundTransferScreen( - creditAccountNo: - users[selectedAccountIndex] - .accountNo!, - remitterName: - users[selectedAccountIndex] - .name!, - // Pass the full list of accounts - accounts: users))); - }, disable: isPaymentDisabled), + AppLocalizations.of(context).fundTransfer, () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => FundTransferScreen( + creditAccountNo: + users[selectedAccountIndex] + .accountNo!, + remitterName: + users[selectedAccountIndex].name!, + // Pass the full list of accounts + accounts: users))); + }, disable: isPaymentDisabled), _buildQuickLink( Symbols.server_person, AppLocalizations.of(context).accountInfo, diff --git a/lib/features/fund_transfer/screens/cooldown.dart b/lib/features/fund_transfer/screens/cooldown.dart index ff1fc47..1d7b8d0 100644 --- a/lib/features/fund_transfer/screens/cooldown.dart +++ b/lib/features/fund_transfer/screens/cooldown.dart @@ -1,90 +1,91 @@ - import 'dart:async'; - import 'package:flutter/material.dart'; +import 'dart:async'; +import 'package:flutter/material.dart'; - class CooldownTimer extends StatefulWidget { - final DateTime createdAt; - final VoidCallback onTimerFinish; +class CooldownTimer extends StatefulWidget { + final DateTime createdAt; + final VoidCallback onTimerFinish; - const CooldownTimer({ - Key? key, - required this.createdAt, - required this.onTimerFinish, - }) : super(key: key); + const CooldownTimer({ + Key? key, + required this.createdAt, + required this.onTimerFinish, + }) : super(key: key); - @override - _CooldownTimerState createState() => _CooldownTimerState(); - } + @override + _CooldownTimerState createState() => _CooldownTimerState(); +} - class _CooldownTimerState extends State { - late Timer _timer; - late Duration _timeRemaining; +class _CooldownTimerState extends State { + late Timer _timer; + late Duration _timeRemaining; - @override - void initState() { - super.initState(); - _updateRemainingTime(); - // Update the timer every second - _timer = Timer.periodic(const Duration(seconds: 1), (timer) { - _updateRemainingTime(); - }); - } + @override + void initState() { + super.initState(); + _updateRemainingTime(); + // Update the timer every second + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + _updateRemainingTime(); + }); + } - void _updateRemainingTime() { - final cooldownEnd = widget.createdAt.add(const Duration(minutes: 60)); - final now = DateTime.now(); + void _updateRemainingTime() { + final cooldownEnd = widget.createdAt.add(const Duration(minutes: 60)); + final now = DateTime.now(); - if (now.isAfter(cooldownEnd)) { - _timeRemaining = Duration.zero; - _timer.cancel(); - // Notify the parent widget that the timer is done - WidgetsBinding.instance.addPostFrameCallback((_) { - widget.onTimerFinish(); - }); - } else { - _timeRemaining = cooldownEnd.difference(now); - } - // Trigger a rebuild - setState(() {}); - } + if (now.isAfter(cooldownEnd)) { + _timeRemaining = Duration.zero; + _timer.cancel(); + // Notify the parent widget that the timer is done + WidgetsBinding.instance.addPostFrameCallback((_) { + widget.onTimerFinish(); + }); + } else { + _timeRemaining = cooldownEnd.difference(now); + } + // Trigger a rebuild + setState(() {}); + } - @override - void dispose() { - _timer.cancel(); - super.dispose(); - } + @override + void dispose() { + _timer.cancel(); + super.dispose(); + } - String _formatDuration(Duration duration) { - final minutes = duration.inMinutes.remainder(60).toString().padLeft(2, '0'); - final seconds = duration.inSeconds.remainder(60).toString().padLeft(2, '0'); - return '$minutes:$seconds'; - } + String _formatDuration(Duration duration) { + final minutes = duration.inMinutes.remainder(60).toString().padLeft(2, '0'); + final seconds = duration.inSeconds.remainder(60).toString().padLeft(2, '0'); + return '$minutes:$seconds'; + } - @override - Widget build(BuildContext context) { - if (_timeRemaining == Duration.zero) { - return const SizedBox.shrink(); // Or some other widget indicating it's enabled - } + @override + Widget build(BuildContext context) { + if (_timeRemaining == Duration.zero) { + return const SizedBox + .shrink(); // Or some other widget indicating it's enabled + } - return Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - 'Enabled after:', - style: TextStyle( - color: Colors.red.shade700, - fontSize: 10, - fontWeight: FontWeight.bold, - ), - ), - Text( - _formatDuration(_timeRemaining), - style: TextStyle( - color: Colors.red.shade700, - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ), - ], - ); - } - } + return Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + 'Enabled after:', + style: TextStyle( + color: Colors.red.shade700, + fontSize: 10, + fontWeight: FontWeight.bold, + ), + ), + Text( + _formatDuration(_timeRemaining), + style: TextStyle( + color: Colors.red.shade700, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + ], + ); + } +} diff --git a/lib/features/fund_transfer/screens/fund_transfer_amount_screen.dart b/lib/features/fund_transfer/screens/fund_transfer_amount_screen.dart index 189266d..602c12a 100644 --- a/lib/features/fund_transfer/screens/fund_transfer_amount_screen.dart +++ b/lib/features/fund_transfer/screens/fund_transfer_amount_screen.dart @@ -43,62 +43,64 @@ class FundTransferAmountScreen extends StatefulWidget { class _FundTransferAmountScreenState extends State { final _limitService = getIt(); -Limit? _limit; -bool _isLoadingLimit = true; -bool _isAmountOverLimit = false; -final _formatCurrency = NumberFormat.currency(locale: 'en_IN', symbol: '₹'); + Limit? _limit; + bool _isLoadingLimit = true; + bool _isAmountOverLimit = false; + final _formatCurrency = NumberFormat.currency(locale: 'en_IN', symbol: '₹'); final _amountController = TextEditingController(); final _remarksController = TextEditingController(); final _formKey = GlobalKey(); TransactionMode _selectedMode = TransactionMode.neft; -@override -void initState() { - super.initState(); - _loadLimit(); // Call the new method - _amountController.addListener(_checkAmountLimit); -} - Future _loadLimit() async { - setState(() { - _isLoadingLimit = true; - }); - try { - final limitData = await _limitService.getLimit(); - setState(() { - _limit = limitData; - _isLoadingLimit = false; - }); - } catch (e) { - // Handle error if needed - setState(() { - _isLoadingLimit = false; - }); - } - } - - // Add this method to check the amount against the limit -void _checkAmountLimit() { - if (_limit == null) return; - - final amount = double.tryParse(_amountController.text) ?? 0; - final remainingLimit = _limit!.dailyLimit - _limit!.usedLimit; - final bool isOverLimit = amount > remainingLimit; - - if (isOverLimit) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Amount exceeds remaining daily limit of ${_formatCurrency.format(remainingLimit)}'), - backgroundColor: Colors.red, - ), - ); + @override + void initState() { + super.initState(); + _loadLimit(); // Call the new method + _amountController.addListener(_checkAmountLimit); } - if (_isAmountOverLimit != isOverLimit) { + Future _loadLimit() async { setState(() { - _isAmountOverLimit = isOverLimit; + _isLoadingLimit = true; }); + try { + final limitData = await _limitService.getLimit(); + setState(() { + _limit = limitData; + _isLoadingLimit = false; + }); + } catch (e) { + // Handle error if needed + setState(() { + _isLoadingLimit = false; + }); + } + } + + // Add this method to check the amount against the limit + void _checkAmountLimit() { + if (_limit == null) return; + + final amount = double.tryParse(_amountController.text) ?? 0; + final remainingLimit = _limit!.dailyLimit - _limit!.usedLimit; + final bool isOverLimit = amount > remainingLimit; + + if (isOverLimit) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Amount exceeds remaining daily limit of ${_formatCurrency.format(remainingLimit)}'), + backgroundColor: Colors.red, + ), + ); + } + + if (_isAmountOverLimit != isOverLimit) { + setState(() { + _isAmountOverLimit = isOverLimit; + }); + } } -} @override void dispose() { @@ -486,27 +488,26 @@ void _checkAmountLimit() { return null; }, ), - const SizedBox(height: 8), - if (_isLoadingLimit) - const Text('Fetching daily limit...'), - if (!_isLoadingLimit && _limit != null) - Text( - 'Remaining Daily Limit: ${_formatCurrency.format(_limit!.dailyLimit - _limit!.usedLimit)}', - style: Theme.of(context).textTheme.bodySmall, - ), + const SizedBox(height: 8), + if (_isLoadingLimit) const Text('Fetching daily limit...'), + if (!_isLoadingLimit && _limit != null) + Text( + 'Remaining Daily Limit: ${_formatCurrency.format(_limit!.dailyLimit - _limit!.usedLimit)}', + style: Theme.of(context).textTheme.bodySmall, + ), const Spacer(), // Proceed Button SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: _isAmountOverLimit ? null : _onProceed, - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), - ), - child: Text(AppLocalizations.of(context).proceed), - ), -), + width: double.infinity, + child: ElevatedButton( + onPressed: _isAmountOverLimit ? null : _onProceed, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), + ), + child: Text(AppLocalizations.of(context).proceed), + ), + ), const SizedBox(height: 10), ], ), diff --git a/lib/features/fund_transfer/screens/fund_transfer_beneficiary_screen.dart b/lib/features/fund_transfer/screens/fund_transfer_beneficiary_screen.dart index 9bd01c7..f8354c0 100644 --- a/lib/features/fund_transfer/screens/fund_transfer_beneficiary_screen.dart +++ b/lib/features/fund_transfer/screens/fund_transfer_beneficiary_screen.dart @@ -73,8 +73,7 @@ class _FundTransferBeneficiaryScreenState ); } - - Widget _buildBeneficiaryList() { + Widget _buildBeneficiaryList() { if (_beneficiaries.isEmpty) { return Center( child: Text(AppLocalizations.of(context).noBeneficiaryFound)); @@ -155,7 +154,6 @@ class _FundTransferBeneficiaryScreenState ); } - @override Widget build(BuildContext context) { return Scaffold( diff --git a/lib/features/fund_transfer/screens/fund_transfer_screen.dart b/lib/features/fund_transfer/screens/fund_transfer_screen.dart index a8640d1..53ad79e 100644 --- a/lib/features/fund_transfer/screens/fund_transfer_screen.dart +++ b/lib/features/fund_transfer/screens/fund_transfer_screen.dart @@ -1,126 +1,126 @@ - import 'package:flutter/material.dart'; - import 'package:flutter_bloc/flutter_bloc.dart'; - import 'package:kmobile/data/models/user.dart'; - import 'package:kmobile/features/auth/controllers/auth_cubit.dart'; - import 'package:kmobile/features/auth/controllers/auth_state.dart'; - import 'package:kmobile/features/fund_transfer/screens/fund_transfer_beneficiary_screen.dart'; - import 'package:kmobile/features/fund_transfer/screens/fund_transfer_self_accounts_screen.dart'; - import 'package:material_symbols_icons/symbols.dart'; - import '../../../l10n/app_localizations.dart'; // Keep localizations +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:kmobile/data/models/user.dart'; +import 'package:kmobile/features/auth/controllers/auth_cubit.dart'; +import 'package:kmobile/features/auth/controllers/auth_state.dart'; +import 'package:kmobile/features/fund_transfer/screens/fund_transfer_beneficiary_screen.dart'; +import 'package:kmobile/features/fund_transfer/screens/fund_transfer_self_accounts_screen.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import '../../../l10n/app_localizations.dart'; // Keep localizations - class FundTransferScreen extends StatelessWidget { - final String creditAccountNo; - final String remitterName; - final List accounts; // Continue to accept the list of accounts +class FundTransferScreen extends StatelessWidget { + final String creditAccountNo; + final String remitterName; + final List accounts; // Continue to accept the list of accounts - const FundTransferScreen({ - super.key, - required this.creditAccountNo, - required this.remitterName, - required this.accounts, // It is passed from the dashboard - }); + const FundTransferScreen({ + super.key, + required this.creditAccountNo, + required this.remitterName, + required this.accounts, // It is passed from the dashboard + }); - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - // Restore localization for the title - title: Text(AppLocalizations.of(context) - .fundTransfer - .replaceFirst(RegExp('\n'), '')), - ), - // Wrap with BlocBuilder to check the authentication state - body: BlocBuilder( - builder: (context, state) { - return ListView( - children: [ - FundTransferManagementTile( - icon: Symbols.person, - // Restore localization for the label - label: "Self Pay", - onTap: () { - // The accounts list is passed directly from the constructor - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => FundTransferSelfAccountsScreen( - debitAccountNo: creditAccountNo, - remitterName: remitterName, - accounts: accounts, - ), - ), - ); - }, - // Disable the tile if the state is not Authenticated - disable: state is! Authenticated, - ), - const Divider(height: 1), - FundTransferManagementTile( - icon: Symbols.input_circle, - // Restore localization for the label - label: AppLocalizations.of(context).ownBank, - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => FundTransferBeneficiaryScreen( - creditAccountNo: creditAccountNo, - remitterName: remitterName, - isOwnBank: true, - ), - ), - ); - }, - ), - const Divider(height: 1), - FundTransferManagementTile( - icon: Symbols.output_circle, - // Restore localization for the label - label: AppLocalizations.of(context).outsideBank, - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => FundTransferBeneficiaryScreen( - creditAccountNo: creditAccountNo, - remitterName: remitterName, - isOwnBank: false, - ), - ), - ); - }, - ), - const Divider(height: 1), - ], - ); - }, - ), - ); - } - } + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + // Restore localization for the title + title: Text(AppLocalizations.of(context) + .fundTransfer + .replaceFirst(RegExp('\n'), '')), + ), + // Wrap with BlocBuilder to check the authentication state + body: BlocBuilder( + builder: (context, state) { + return ListView( + children: [ + FundTransferManagementTile( + icon: Symbols.person, + // Restore localization for the label + label: "Self Pay", + onTap: () { + // The accounts list is passed directly from the constructor + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => FundTransferSelfAccountsScreen( + debitAccountNo: creditAccountNo, + remitterName: remitterName, + accounts: accounts, + ), + ), + ); + }, + // Disable the tile if the state is not Authenticated + disable: state is! Authenticated, + ), + const Divider(height: 1), + FundTransferManagementTile( + icon: Symbols.input_circle, + // Restore localization for the label + label: AppLocalizations.of(context).ownBank, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => FundTransferBeneficiaryScreen( + creditAccountNo: creditAccountNo, + remitterName: remitterName, + isOwnBank: true, + ), + ), + ); + }, + ), + const Divider(height: 1), + FundTransferManagementTile( + icon: Symbols.output_circle, + // Restore localization for the label + label: AppLocalizations.of(context).outsideBank, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => FundTransferBeneficiaryScreen( + creditAccountNo: creditAccountNo, + remitterName: remitterName, + isOwnBank: false, + ), + ), + ); + }, + ), + const Divider(height: 1), + ], + ); + }, + ), + ); + } +} - class FundTransferManagementTile extends StatelessWidget { - final IconData icon; - final String label; - final VoidCallback onTap; - final bool disable; +class FundTransferManagementTile extends StatelessWidget { + final IconData icon; + final String label; + final VoidCallback onTap; + final bool disable; - const FundTransferManagementTile({ - super.key, - required this.icon, - required this.label, - required this.onTap, - this.disable = false, - }); + const FundTransferManagementTile({ + super.key, + required this.icon, + required this.label, + required this.onTap, + this.disable = false, + }); - @override - Widget build(BuildContext context) { - return ListTile( - leading: Icon(icon), - title: Text(label), - trailing: const Icon(Symbols.arrow_right, size: 20), - onTap: onTap, - enabled: !disable, - ); - } - } \ No newline at end of file + @override + Widget build(BuildContext context) { + return ListTile( + leading: Icon(icon), + title: Text(label), + trailing: const Icon(Symbols.arrow_right, size: 20), + onTap: onTap, + enabled: !disable, + ); + } +} diff --git a/lib/features/fund_transfer/screens/fund_transfer_self_accounts_screen.dart b/lib/features/fund_transfer/screens/fund_transfer_self_accounts_screen.dart index 583fc89..4d002db 100644 --- a/lib/features/fund_transfer/screens/fund_transfer_self_accounts_screen.dart +++ b/lib/features/fund_transfer/screens/fund_transfer_self_accounts_screen.dart @@ -1,94 +1,93 @@ - import 'package:flutter/material.dart'; - import 'package:kmobile/data/models/user.dart'; - import 'package:kmobile/features/fund_transfer/screens/fund_transfer_self_amount_screen.dart'; - import 'package:kmobile/widgets/bank_logos.dart'; +import 'package:flutter/material.dart'; +import 'package:kmobile/data/models/user.dart'; +import 'package:kmobile/features/fund_transfer/screens/fund_transfer_self_amount_screen.dart'; +import 'package:kmobile/widgets/bank_logos.dart'; - class FundTransferSelfAccountsScreen extends StatelessWidget { - final String debitAccountNo; - final String remitterName; - final List accounts; +class FundTransferSelfAccountsScreen extends StatelessWidget { + final String debitAccountNo; + final String remitterName; + final List accounts; - const FundTransferSelfAccountsScreen({ - super.key, - required this.debitAccountNo, - required this.remitterName, - required this.accounts, - }); + const FundTransferSelfAccountsScreen({ + super.key, + required this.debitAccountNo, + required this.remitterName, + required this.accounts, + }); - // Helper function to get the full account type name from the short code - String _getFullAccountType(String? accountType) { - if (accountType == null || accountType.isEmpty) return 'N/A'; - switch (accountType.toLowerCase()) { - case 'sa': - case 'sb': - return "Savings Account"; - case 'ln': - return "Loan Account"; - case 'td': - return "Term Deposit"; - case 'rd': - return "Recurring Deposit"; - case 'ca': - return "Current Account"; - default: - return "Unknown Account"; - } - } + // Helper function to get the full account type name from the short code + String _getFullAccountType(String? accountType) { + if (accountType == null || accountType.isEmpty) return 'N/A'; + switch (accountType.toLowerCase()) { + case 'sa': + case 'sb': + return "Savings Account"; + case 'ln': + return "Loan Account"; + case 'td': + return "Term Deposit"; + case 'rd': + return "Recurring Deposit"; + case 'ca': + return "Current Account"; + default: + return "Unknown Account"; + } + } - @override - Widget build(BuildContext context) { - // Filter out the account from which the transfer is being made - final filteredAccounts = - accounts.where((acc) => acc.accountNo != debitAccountNo).toList(); + @override + Widget build(BuildContext context) { + // Filter out the account from which the transfer is being made + final filteredAccounts = + accounts.where((acc) => acc.accountNo != debitAccountNo).toList(); - return Scaffold( - appBar: AppBar( - title: const Text("Select Account"), - ), - body: filteredAccounts.isEmpty - ? const Center( - child: Text("No other accounts found"), - ) - : ListView.builder( - itemCount: filteredAccounts.length, - itemBuilder: (context, index) { - final account = filteredAccounts[index]; - return ListTile( - leading: CircleAvatar( - radius: 24, - backgroundColor: Colors.transparent, - child: - getBankLogo('Kangra Central Co-operative Bank', context), - ), - title: Text(account.name ?? 'N/A'), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(account.accountNo ?? 'N/A'), - Text( - _getFullAccountType(account.accountType), - style: - TextStyle(fontSize: 12, color: Colors.grey[600]), - ), - ], - ), - onTap: () { - // Navigate to the amount screen, passing the selected User object directly. - // No Beneficiary object is created. - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => FundTransferSelfAmountScreen( - debitAccountNo: debitAccountNo, - creditAccount: account, // Pass the User object - remitterName: remitterName, - ), - ), - ); - }, - ); - }, - ), - ); - } - } \ No newline at end of file + return Scaffold( + appBar: AppBar( + title: const Text("Select Account"), + ), + body: filteredAccounts.isEmpty + ? const Center( + child: Text("No other accounts found"), + ) + : ListView.builder( + itemCount: filteredAccounts.length, + itemBuilder: (context, index) { + final account = filteredAccounts[index]; + return ListTile( + leading: CircleAvatar( + radius: 24, + backgroundColor: Colors.transparent, + child: getBankLogo( + 'Kangra Central Co-operative Bank', context), + ), + title: Text(account.name ?? 'N/A'), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(account.accountNo ?? 'N/A'), + Text( + _getFullAccountType(account.accountType), + style: TextStyle(fontSize: 12, color: Colors.grey[600]), + ), + ], + ), + onTap: () { + // Navigate to the amount screen, passing the selected User object directly. + // No Beneficiary object is created. + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => FundTransferSelfAmountScreen( + debitAccountNo: debitAccountNo, + creditAccount: account, // Pass the User object + remitterName: remitterName, + ), + ), + ); + }, + ); + }, + ), + ); + } +} diff --git a/lib/features/fund_transfer/screens/fund_transfer_self_amount_screen.dart b/lib/features/fund_transfer/screens/fund_transfer_self_amount_screen.dart index 6b4f1f1..791c747 100644 --- a/lib/features/fund_transfer/screens/fund_transfer_self_amount_screen.dart +++ b/lib/features/fund_transfer/screens/fund_transfer_self_amount_screen.dart @@ -1,245 +1,244 @@ - import 'package:flutter/material.dart'; - import 'package:intl/intl.dart'; - import 'package:kmobile/api/services/limit_service.dart'; - import 'package:kmobile/api/services/payment_service.dart'; - import 'package:kmobile/data/models/transfer.dart'; - import 'package:kmobile/data/models/user.dart'; - import 'package:kmobile/di/injection.dart'; - import 'package:kmobile/features/fund_transfer/screens/payment_animation.dart'; - import 'package:kmobile/features/fund_transfer/screens/transaction_pin_screen.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:kmobile/api/services/limit_service.dart'; +import 'package:kmobile/api/services/payment_service.dart'; +import 'package:kmobile/data/models/transfer.dart'; +import 'package:kmobile/data/models/user.dart'; +import 'package:kmobile/di/injection.dart'; +import 'package:kmobile/features/fund_transfer/screens/payment_animation.dart'; +import 'package:kmobile/features/fund_transfer/screens/transaction_pin_screen.dart'; import 'package:kmobile/widgets/bank_logos.dart'; - class FundTransferSelfAmountScreen extends StatefulWidget { - final String debitAccountNo; - final User creditAccount; - final String remitterName; +class FundTransferSelfAmountScreen extends StatefulWidget { + final String debitAccountNo; + final User creditAccount; + final String remitterName; - const FundTransferSelfAmountScreen({ - super.key, - required this.debitAccountNo, - required this.creditAccount, - required this.remitterName, - }); + const FundTransferSelfAmountScreen({ + super.key, + required this.debitAccountNo, + required this.creditAccount, + required this.remitterName, + }); - @override - State createState() => - _FundTransferSelfAmountScreenState(); - } + @override + State createState() => + _FundTransferSelfAmountScreenState(); +} - class _FundTransferSelfAmountScreenState - extends State { - final _formKey = GlobalKey(); - final _amountController = TextEditingController(); - final _remarksController = TextEditingController(); +class _FundTransferSelfAmountScreenState + extends State { + final _formKey = GlobalKey(); + final _amountController = TextEditingController(); + final _remarksController = TextEditingController(); - // --- Limit Checking Variables --- - final _limitService = getIt(); - Limit? _limit; - bool _isLoadingLimit = true; - bool _isAmountOverLimit = false; - final _formatCurrency = NumberFormat.currency(locale: 'en_IN', symbol: '₹'); + // --- Limit Checking Variables --- + final _limitService = getIt(); + Limit? _limit; + bool _isLoadingLimit = true; + bool _isAmountOverLimit = false; + final _formatCurrency = NumberFormat.currency(locale: 'en_IN', symbol: '₹'); - - @override - void initState() { - super.initState(); - _loadLimit(); // Fetch the daily limit - _amountController.addListener(_checkAmountLimit); // Listen for amount changes - } - - @override - void dispose() { - _amountController.removeListener(_checkAmountLimit); - _amountController.dispose(); - _remarksController.dispose(); - super.dispose(); - } - - Future _loadLimit() async { - setState(() { - _isLoadingLimit = true; - }); - try { - final limitData = await _limitService.getLimit(); - setState(() { - _limit = limitData; - _isLoadingLimit = false; - }); - } catch (e) { - setState(() { - _isLoadingLimit = false; - }); - } - } - - void _checkAmountLimit() { - if (_limit == null) return; - - final amount = double.tryParse(_amountController.text) ?? 0; - final remainingLimit = _limit!.dailyLimit - _limit!.usedLimit; - final bool isOverLimit = amount > remainingLimit; - - if (isOverLimit) { - WidgetsBinding.instance.addPostFrameCallback((_) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - 'Amount exceeds remaining daily limit of ${_formatCurrency.format(remainingLimit)}'), - backgroundColor: Colors.red, - ), - ); - }); - } - - if (_isAmountOverLimit != isOverLimit) { - setState(() { - _isAmountOverLimit = isOverLimit; - }); - } - } - - void _onProceed() { - if (_formKey.currentState!.validate()) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => TransactionPinScreen( - onPinCompleted: (pinScreenContext, tpin) async { - final transfer = Transfer( - fromAccount: widget.debitAccountNo, - toAccount: widget.creditAccount.accountNo!, - toAccountType: 'Savings', // Assuming 'SB' for savings - amount: _amountController.text, - tpin: tpin, - ); - - final paymentService = getIt(); - final paymentResponseFuture = - paymentService.processQuickPayWithinBank(transfer); - - Navigator.of(pinScreenContext).pushReplacement( - MaterialPageRoute( - builder: (_) => - PaymentAnimationScreen(paymentResponse: paymentResponseFuture), - ), - ); - }, - ), - ), - ); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text("Fund Transfer"), - ), - body: SafeArea( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Debit Account (User) - Text( - "Debit From", - style: Theme.of(context).textTheme.titleSmall, - ), - Card( - elevation: 0, - margin: const EdgeInsets.symmetric(vertical: 8.0), - child: ListTile( - leading: Image.asset( - 'assets/images/logo.png', - width: 40, - height: 40, - ), - title: Text(widget.remitterName), - subtitle: Text(widget.debitAccountNo), - ), - ), - const SizedBox(height: 24), - - // Credit Account (Self) - Text( - "Credited To", - style: Theme.of(context).textTheme.titleSmall, - ), - Card( - elevation: 0, - margin: const EdgeInsets.symmetric(vertical: 8.0), - child: ListTile( - leading: - getBankLogo('Kangra Central Co-operative Bank', context), - title: Text(widget.creditAccount.name ?? 'N/A'), - subtitle: Text(widget.creditAccount.accountNo ?? 'N/A'), - ), - ), - const SizedBox(height: 24), - - // Remarks - TextFormField( - controller: _remarksController, - decoration: const InputDecoration( - labelText: "Remarks (Optional)", - border: OutlineInputBorder(), - ), - ), - const SizedBox(height: 24), - - // Amount - TextFormField( - controller: _amountController, - keyboardType: TextInputType.number, - decoration: const InputDecoration( - labelText: "Amount", - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.currency_rupee), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return "Amount is required"; - } - if (double.tryParse(value) == null || - double.parse(value) <= 0) { - return "Please enter a valid amount"; - } - return null; - }, - ), - const SizedBox(height: 8), - - // Daily Limit Display - if (_isLoadingLimit) - const Text('Fetching daily limit...'), - if (!_isLoadingLimit && _limit != null) - Text( - 'Remaining Daily Limit: ${_formatCurrency.format(_limit!.dailyLimit - _limit!.usedLimit)}', - style: Theme.of(context).textTheme.bodySmall, - ), - const Spacer(), - - // Proceed Button - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: _isAmountOverLimit ? null : _onProceed, - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), - ), - child: const Text("Proceed"), - ), - ), - const SizedBox(height: 10), - ], - ), - ), - ), - ), - ); - } + @override + void initState() { + super.initState(); + _loadLimit(); // Fetch the daily limit + _amountController + .addListener(_checkAmountLimit); // Listen for amount changes } + + @override + void dispose() { + _amountController.removeListener(_checkAmountLimit); + _amountController.dispose(); + _remarksController.dispose(); + super.dispose(); + } + + Future _loadLimit() async { + setState(() { + _isLoadingLimit = true; + }); + try { + final limitData = await _limitService.getLimit(); + setState(() { + _limit = limitData; + _isLoadingLimit = false; + }); + } catch (e) { + setState(() { + _isLoadingLimit = false; + }); + } + } + + void _checkAmountLimit() { + if (_limit == null) return; + + final amount = double.tryParse(_amountController.text) ?? 0; + final remainingLimit = _limit!.dailyLimit - _limit!.usedLimit; + final bool isOverLimit = amount > remainingLimit; + + if (isOverLimit) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Amount exceeds remaining daily limit of ${_formatCurrency.format(remainingLimit)}'), + backgroundColor: Colors.red, + ), + ); + }); + } + + if (_isAmountOverLimit != isOverLimit) { + setState(() { + _isAmountOverLimit = isOverLimit; + }); + } + } + + void _onProceed() { + if (_formKey.currentState!.validate()) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TransactionPinScreen( + onPinCompleted: (pinScreenContext, tpin) async { + final transfer = Transfer( + fromAccount: widget.debitAccountNo, + toAccount: widget.creditAccount.accountNo!, + toAccountType: 'Savings', // Assuming 'SB' for savings + amount: _amountController.text, + tpin: tpin, + ); + + final paymentService = getIt(); + final paymentResponseFuture = + paymentService.processQuickPayWithinBank(transfer); + + Navigator.of(pinScreenContext).pushReplacement( + MaterialPageRoute( + builder: (_) => PaymentAnimationScreen( + paymentResponse: paymentResponseFuture), + ), + ); + }, + ), + ), + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Fund Transfer"), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Debit Account (User) + Text( + "Debit From", + style: Theme.of(context).textTheme.titleSmall, + ), + Card( + elevation: 0, + margin: const EdgeInsets.symmetric(vertical: 8.0), + child: ListTile( + leading: Image.asset( + 'assets/images/logo.png', + width: 40, + height: 40, + ), + title: Text(widget.remitterName), + subtitle: Text(widget.debitAccountNo), + ), + ), + const SizedBox(height: 24), + + // Credit Account (Self) + Text( + "Credited To", + style: Theme.of(context).textTheme.titleSmall, + ), + Card( + elevation: 0, + margin: const EdgeInsets.symmetric(vertical: 8.0), + child: ListTile( + leading: getBankLogo( + 'Kangra Central Co-operative Bank', context), + title: Text(widget.creditAccount.name ?? 'N/A'), + subtitle: Text(widget.creditAccount.accountNo ?? 'N/A'), + ), + ), + const SizedBox(height: 24), + + // Remarks + TextFormField( + controller: _remarksController, + decoration: const InputDecoration( + labelText: "Remarks (Optional)", + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 24), + + // Amount + TextFormField( + controller: _amountController, + keyboardType: TextInputType.number, + decoration: const InputDecoration( + labelText: "Amount", + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.currency_rupee), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return "Amount is required"; + } + if (double.tryParse(value) == null || + double.parse(value) <= 0) { + return "Please enter a valid amount"; + } + return null; + }, + ), + const SizedBox(height: 8), + + // Daily Limit Display + if (_isLoadingLimit) const Text('Fetching daily limit...'), + if (!_isLoadingLimit && _limit != null) + Text( + 'Remaining Daily Limit: ${_formatCurrency.format(_limit!.dailyLimit - _limit!.usedLimit)}', + style: Theme.of(context).textTheme.bodySmall, + ), + const Spacer(), + + // Proceed Button + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _isAmountOverLimit ? null : _onProceed, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), + ), + child: const Text("Proceed"), + ), + ), + const SizedBox(height: 10), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/features/profile/daily_transaction_limit.dart b/lib/features/profile/daily_transaction_limit.dart index 9e56d6d..dfd4d75 100644 --- a/lib/features/profile/daily_transaction_limit.dart +++ b/lib/features/profile/daily_transaction_limit.dart @@ -16,24 +16,24 @@ class _DailyLimitScreenState extends State { double? _spentAmount = 0.0; final _limitController = TextEditingController(); var service = getIt(); - Limit? limit; - bool _isLoading = true; + Limit? limit; + bool _isLoading = true; @override void initState() { super.initState(); _loadlimits(); } - + Future _loadlimits() async { - setState(() { - _isLoading = true; - }); + setState(() { + _isLoading = true; + }); final limit_data = await service.getLimit(); setState(() { - limit = limit_data; - _isLoading = false; - }); + limit = limit_data; + _isLoading = false; + }); } @override @@ -42,74 +42,74 @@ class _DailyLimitScreenState extends State { super.dispose(); } -Future _showAddOrEditLimitDialog() async { - _limitController.text = _currentLimit?.toStringAsFixed(0) ?? ''; - final newLimit = await showDialog( - context: context, - builder: (dialogContext) { - final localizations = AppLocalizations.of(dialogContext); - final theme = Theme.of(dialogContext); - return AlertDialog( - title: Text( - _currentLimit == null - ? localizations.addLimit - : localizations.editLimit, - ), - content: TextField( - controller: _limitController, - autofocus: true, - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp(r'^\d+')), + Future _showAddOrEditLimitDialog() async { + _limitController.text = _currentLimit?.toStringAsFixed(0) ?? ''; + final newLimit = await showDialog( + context: context, + builder: (dialogContext) { + final localizations = AppLocalizations.of(dialogContext); + final theme = Theme.of(dialogContext); + return AlertDialog( + title: Text( + _currentLimit == null + ? localizations.addLimit + : localizations.editLimit, + ), + content: TextField( + controller: _limitController, + autofocus: true, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'^\d+')), + ], + decoration: InputDecoration( + labelText: localizations.limitAmount, + prefixText: '₹', + border: const OutlineInputBorder(), + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(dialogContext).pop(), + child: Text(localizations.cancel), + ), + ElevatedButton( + onPressed: () { + final value = double.tryParse(_limitController.text); + if (value == null || value <= 0) return; + + if (value > 200000) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text( + "Limit To be Set must be less than 200000"), + behavior: SnackBarBehavior.floating, + backgroundColor: theme.colorScheme.error, + ), + ); + } else { + service.editLimit(value); + Navigator.of(dialogContext).pop(value); + } + }, + child: Text(localizations.save), + ), ], - decoration: InputDecoration( - labelText: localizations.limitAmount, - prefixText: '₹', - border: const OutlineInputBorder(), - ), - ), - actions: [ - TextButton( - onPressed: () => Navigator.of(dialogContext).pop(), - child: Text(localizations.cancel), - ), - ElevatedButton( - onPressed: () { - final value = double.tryParse(_limitController.text); - if (value == null || value <= 0) return; - - if (value > 200000) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: const Text("Limit To be Set must be less than 200000"), - behavior: SnackBarBehavior.floating, - backgroundColor: theme.colorScheme.error, - ), - ); - } else { - service.editLimit(value); - Navigator.of(dialogContext).pop(value); - } - }, - child: Text(localizations.save), - ), - ], - ); - }, - ); - - if (newLimit != null) { - _loadlimits(); - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text("Limit Updated"), - behavior: SnackBarBehavior.floating, - ), + ); + }, ); - } -} + if (newLimit != null) { + _loadlimits(); + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("Limit Updated"), + behavior: SnackBarBehavior.floating, + ), + ); + } + } void _removeLimit() { setState(() { @@ -117,112 +117,113 @@ Future _showAddOrEditLimitDialog() async { }); } - @override - Widget build(BuildContext context) { - if (_isLoading) { - final localizations = AppLocalizations.of(context); - return Scaffold( - appBar: AppBar( - title: Text(localizations.dailylimit), - ), - body: const Center( - child: CircularProgressIndicator(), - ), - ); -} + @override + Widget build(BuildContext context) { + if (_isLoading) { + final localizations = AppLocalizations.of(context); + return Scaffold( + appBar: AppBar( + title: Text(localizations.dailylimit), + ), + body: const Center( + child: CircularProgressIndicator(), + ), + ); + } _currentLimit = limit?.dailyLimit; _spentAmount = limit?.usedLimit; - final localizations = AppLocalizations.of(context); - final theme = Theme.of(context); - final formatCurrency = NumberFormat.currency(locale: 'en_IN', symbol: '₹'); - final remainingLimit = _currentLimit != null ? _currentLimit! - _spentAmount! : 0.0; + final localizations = AppLocalizations.of(context); + final theme = Theme.of(context); + final formatCurrency = NumberFormat.currency(locale: 'en_IN', symbol: '₹'); + final remainingLimit = + _currentLimit != null ? _currentLimit! - _spentAmount! : 0.0; - return Scaffold( - appBar: AppBar( - title: Text(localizations.dailylimit), - ), - body: Center( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - localizations.currentDailyLimit, - style: theme.textTheme.headlineSmall?.copyWith( - color: theme.colorScheme.onSurface.withOpacity(0.7), - ), - ), - const SizedBox(height: 16), - Text( - _currentLimit == null - ? localizations.noLimitSet - : formatCurrency.format(_currentLimit), - style: theme.textTheme.headlineMedium?.copyWith( - fontWeight: FontWeight.bold, - color: _currentLimit == null - ? theme.colorScheme.secondary - : theme.colorScheme.primary, - ), - ), - if (_currentLimit != null) ...[ - const SizedBox(height: 24), - Text( - "Remaining Limit Today", // This should be localized - style: theme.textTheme.titleMedium, - ), - const SizedBox(height: 4), - Text( - formatCurrency.format(remainingLimit), - style: theme.textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.bold, - color: remainingLimit > 0 - ? Colors.green - : theme.colorScheme.error, - ), - ), - ], - const SizedBox(height: 48), - if (_currentLimit == null) - ElevatedButton.icon( - onPressed: _showAddOrEditLimitDialog, - icon: const Icon(Icons.add_circle_outline), - label: Text(localizations.addLimit), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric( - horizontal: 24, vertical: 12), - textStyle: theme.textTheme.titleMedium, - ), - ) - else - Column( - children: [ - ElevatedButton.icon( - onPressed: _showAddOrEditLimitDialog, - icon: const Icon(Icons.edit_outlined), - label: Text(localizations.editLimit), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric( - horizontal: 24, vertical: 12), - textStyle: theme.textTheme.titleMedium, - ), - ), - const SizedBox(height: 16), - // TextButton.icon( - // onPressed: _removeLimit, - // icon: const Icon(Icons.remove_circle_outline), - // label: Text(localizations.removeLimit), - // style: TextButton.styleFrom( - // foregroundColor: theme.colorScheme.error, - // ), - // ), - ], - ), - ], - ), - ), - ), - ); - } + return Scaffold( + appBar: AppBar( + title: Text(localizations.dailylimit), + ), + body: Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + localizations.currentDailyLimit, + style: theme.textTheme.headlineSmall?.copyWith( + color: theme.colorScheme.onSurface.withOpacity(0.7), + ), + ), + const SizedBox(height: 16), + Text( + _currentLimit == null + ? localizations.noLimitSet + : formatCurrency.format(_currentLimit), + style: theme.textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.bold, + color: _currentLimit == null + ? theme.colorScheme.secondary + : theme.colorScheme.primary, + ), + ), + if (_currentLimit != null) ...[ + const SizedBox(height: 24), + Text( + "Remaining Limit Today", // This should be localized + style: theme.textTheme.titleMedium, + ), + const SizedBox(height: 4), + Text( + formatCurrency.format(remainingLimit), + style: theme.textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.bold, + color: remainingLimit > 0 + ? Colors.green + : theme.colorScheme.error, + ), + ), + ], + const SizedBox(height: 48), + if (_currentLimit == null) + ElevatedButton.icon( + onPressed: _showAddOrEditLimitDialog, + icon: const Icon(Icons.add_circle_outline), + label: Text(localizations.addLimit), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 24, vertical: 12), + textStyle: theme.textTheme.titleMedium, + ), + ) + else + Column( + children: [ + ElevatedButton.icon( + onPressed: _showAddOrEditLimitDialog, + icon: const Icon(Icons.edit_outlined), + label: Text(localizations.editLimit), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 24, vertical: 12), + textStyle: theme.textTheme.titleMedium, + ), + ), + const SizedBox(height: 16), + // TextButton.icon( + // onPressed: _removeLimit, + // icon: const Icon(Icons.remove_circle_outline), + // label: Text(localizations.removeLimit), + // style: TextButton.styleFrom( + // foregroundColor: theme.colorScheme.error, + // ), + // ), + ], + ), + ], + ), + ), + ), + ); + } } diff --git a/lib/features/profile/profile_screen.dart b/lib/features/profile/profile_screen.dart index f3b9d38..5f3a19e 100644 --- a/lib/features/profile/profile_screen.dart +++ b/lib/features/profile/profile_screen.dart @@ -16,7 +16,6 @@ import 'package:kmobile/features/profile/preferences/preference_screen.dart'; import 'package:kmobile/api/services/auth_service.dart'; import 'package:kmobile/features/fund_transfer/screens/tpin_set_screen.dart'; - class ProfileScreen extends StatefulWidget { final String mobileNumber; const ProfileScreen({super.key, required this.mobileNumber}); @@ -38,12 +37,12 @@ class _ProfileScreenState extends State { return 'Version ${info.version} (${info.buildNumber})'; } - Future _loadBiometricStatus() async { - final prefs = await SharedPreferences.getInstance(); - setState(() { - _isBiometricEnabled = prefs.getBool('biometric_enabled') ?? false; - }); - } + Future _loadBiometricStatus() async { + final prefs = await SharedPreferences.getInstance(); + setState(() { + _isBiometricEnabled = prefs.getBool('biometric_enabled') ?? false; + }); + } Future _handleLogout(BuildContext context) async { final auth = getIt(); @@ -54,90 +53,90 @@ class _ProfileScreenState extends State { Navigator.pushNamedAndRemoveUntil(context, '/login', (route) => false); } - Future _handleBiometricToggle(bool enable) async { - final localAuth = LocalAuthentication(); - final prefs = await SharedPreferences.getInstance(); - final canCheck = await localAuth.canCheckBiometrics; + Future _handleBiometricToggle(bool enable) async { + final localAuth = LocalAuthentication(); + final prefs = await SharedPreferences.getInstance(); + final canCheck = await localAuth.canCheckBiometrics; - if (!canCheck) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(AppLocalizations.of(context).biometricsNotAvailable)), - ); - return; - } + if (!canCheck) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context).biometricsNotAvailable)), + ); + return; + } - if (enable) { - final optIn = await showDialog( - context: context, - barrierDismissible: false, - builder: (ctx) => AlertDialog( - title: Text(AppLocalizations.of(context).enableFingerprintLogin), - content: Text(AppLocalizations.of(context).enableFingerprintMessage), - actions: [ - TextButton( - onPressed: () => Navigator.of(ctx).pop(false), - child: Text(AppLocalizations.of(context).no), - ), - TextButton( - onPressed: () => Navigator.of(ctx).pop(true), - child: Text(AppLocalizations.of(context).yes), - ), - ], - ), - ); + if (enable) { + final optIn = await showDialog( + context: context, + barrierDismissible: false, + builder: (ctx) => AlertDialog( + title: Text(AppLocalizations.of(context).enableFingerprintLogin), + content: Text(AppLocalizations.of(context).enableFingerprintMessage), + actions: [ + TextButton( + onPressed: () => Navigator.of(ctx).pop(false), + child: Text(AppLocalizations.of(context).no), + ), + TextButton( + onPressed: () => Navigator.of(ctx).pop(true), + child: Text(AppLocalizations.of(context).yes), + ), + ], + ), + ); - if (optIn == true) { - try { - final didAuth = await localAuth.authenticate( - localizedReason: AppLocalizations.of(context).authenticateToEnable, - options: const AuthenticationOptions( - stickyAuth: true, - biometricOnly: true, - ), - ); - if (didAuth) { - await prefs.setBool('biometric_enabled', true); - if (mounted) { - setState(() { - _isBiometricEnabled = true; - }); - } - } - } catch (e) { - // Handle exceptions, state remains unchanged. - } - } - } else { - final optOut = await showDialog( - context: context, - barrierDismissible: false, - builder: (ctx) => AlertDialog( - title: Text(AppLocalizations.of(context).disableFingerprintLogin), - content: Text(AppLocalizations.of(context).disableFingerprintMessage), - actions: [ - TextButton( - onPressed: () => Navigator.of(ctx).pop(false), - child: Text(AppLocalizations.of(context).no), - ), - TextButton( - onPressed: () => Navigator.of(ctx).pop(true), - child: Text(AppLocalizations.of(context).yes), - ), - ], - ), - ); + if (optIn == true) { + try { + final didAuth = await localAuth.authenticate( + localizedReason: AppLocalizations.of(context).authenticateToEnable, + options: const AuthenticationOptions( + stickyAuth: true, + biometricOnly: true, + ), + ); + if (didAuth) { + await prefs.setBool('biometric_enabled', true); + if (mounted) { + setState(() { + _isBiometricEnabled = true; + }); + } + } + } catch (e) { + // Handle exceptions, state remains unchanged. + } + } + } else { + final optOut = await showDialog( + context: context, + barrierDismissible: false, + builder: (ctx) => AlertDialog( + title: Text(AppLocalizations.of(context).disableFingerprintLogin), + content: Text(AppLocalizations.of(context).disableFingerprintMessage), + actions: [ + TextButton( + onPressed: () => Navigator.of(ctx).pop(false), + child: Text(AppLocalizations.of(context).no), + ), + TextButton( + onPressed: () => Navigator.of(ctx).pop(true), + child: Text(AppLocalizations.of(context).yes), + ), + ], + ), + ); - if (optOut == true) { - await prefs.setBool('biometric_enabled', false); - if (mounted) { - setState(() { - _isBiometricEnabled = false; - }); - } - } - } - } + if (optOut == true) { + await prefs.setBool('biometric_enabled', false); + if (mounted) { + setState(() { + _isBiometricEnabled = false; + }); + } + } + } + } @override Widget build(BuildContext context) { @@ -172,14 +171,14 @@ class _ProfileScreenState extends State { }, ), 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), - ), + 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.password), title: Text(loc.changeLoginPassword), @@ -195,55 +194,57 @@ class _ProfileScreenState extends State { ), ListTile( leading: const Icon(Icons.password), - title: Text('Change TPIN'), - onTap: () async { - // 1. Get the AuthService instance - final authService = getIt(); + title: Text('Change TPIN'), + onTap: () async { + // 1. Get the AuthService instance + final authService = getIt(); - // 2. Call checkTpin() to see if TPIN is set - final isTpinSet = await authService.checkTpin(); + // 2. Call checkTpin() to see if TPIN is set + final isTpinSet = await authService.checkTpin(); - // 3. If TPIN is not set, show the dialog - if (!isTpinSet) { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text('TPIN Not Set'), - content: Text('You have not set a TPIN yet. Please set a TPIN to proceed.'), - actions: [ - TextButton( - child: Text('Back'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: Text('Proceed'), - onPressed: () { - Navigator.of(context).pop(); // Dismiss the dialog - // Navigate to the TPIN set screen - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => TpinSetScreen(), - ), - ); - }, - ), - ], - ); - }, - ); - } else { - // Case 2: TPIN is set -Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => ChangeTpinScreen(mobileNumber: widget.mobileNumber), - ), -); - } - }, -), + // 3. If TPIN is not set, show the dialog + if (!isTpinSet) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('TPIN Not Set'), + content: Text( + 'You have not set a TPIN yet. Please set a TPIN to proceed.'), + actions: [ + TextButton( + child: Text('Back'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: Text('Proceed'), + onPressed: () { + Navigator.of(context).pop(); // Dismiss the dialog + // Navigate to the TPIN set screen + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TpinSetScreen(), + ), + ); + }, + ), + ], + ); + }, + ); + } else { + // Case 2: TPIN is set + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + ChangeTpinScreen(mobileNumber: widget.mobileNumber), + ), + ); + } + }, + ), // ListTile( // leading: const Icon(Icons.password), // title: const Text("Change Login MPIN"), diff --git a/lib/features/profile/tpin/change_tpin_otp_screen.dart b/lib/features/profile/tpin/change_tpin_otp_screen.dart index d4d8d66..193ccd6 100644 --- a/lib/features/profile/tpin/change_tpin_otp_screen.dart +++ b/lib/features/profile/tpin/change_tpin_otp_screen.dart @@ -1,125 +1,125 @@ - import 'package:flutter/material.dart'; - import 'package:kmobile/di/injection.dart'; - import 'package:kmobile/widgets/pin_input_field.dart'; - import '../../../api/services/change_password_service.dart'; +import 'package:flutter/material.dart'; +import 'package:kmobile/di/injection.dart'; +import 'package:kmobile/widgets/pin_input_field.dart'; +import '../../../api/services/change_password_service.dart'; - class ChangeTpinOtpScreen extends StatefulWidget { - final String oldTpin; - final String newTpin; - final String mobileNumber; +class ChangeTpinOtpScreen extends StatefulWidget { + final String oldTpin; + final String newTpin; + final String mobileNumber; - const ChangeTpinOtpScreen({ - super.key, - required this.oldTpin, - required this.newTpin, - required this.mobileNumber, - }); + const ChangeTpinOtpScreen({ + super.key, + required this.oldTpin, + required this.newTpin, + required this.mobileNumber, + }); - @override - State createState() => _ChangeTpinOtpScreenState(); - } + @override + State createState() => _ChangeTpinOtpScreenState(); +} - class _ChangeTpinOtpScreenState extends State { - final _otpController = TextEditingController(); - final ChangePasswordService _changePasswordService = - getIt(); - bool _isLoading = false; +class _ChangeTpinOtpScreenState extends State { + final _otpController = TextEditingController(); + final ChangePasswordService _changePasswordService = + getIt(); + bool _isLoading = false; - void _handleVerifyOtp() async { - if (_otpController.text.length != 6) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Please enter a valid 6-digit OTP')), - ); - return; - } + void _handleVerifyOtp() async { + if (_otpController.text.length != 6) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Please enter a valid 6-digit OTP')), + ); + return; + } - setState(() { - _isLoading = true; - }); + setState(() { + _isLoading = true; + }); - try { - // 1. Validate the OTP first. - await _changePasswordService.validateOtp( - otp: _otpController.text, - mobileNumber: widget.mobileNumber, - ); + try { + // 1. Validate the OTP first. + await _changePasswordService.validateOtp( + otp: _otpController.text, + mobileNumber: widget.mobileNumber, + ); - // 2. If OTP is valid, then call validateChangeTpin. - await _changePasswordService.validateChangeTpin( - oldTpin: widget.oldTpin, - newTpin: widget.newTpin, - ); + // 2. If OTP is valid, then call validateChangeTpin. + await _changePasswordService.validateChangeTpin( + oldTpin: widget.oldTpin, + newTpin: widget.newTpin, + ); - // 3. Show success message. - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('TPIN changed successfully!'), - backgroundColor: Colors.green, - ), - ); - // 4. Navigate back to the profile screen or home. - Navigator.of(context).popUntil((route) => route.isFirst); - } - } catch (e) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('An error occurred: $e'), - backgroundColor: Colors.red, - ), - ); - } - } finally { - if (mounted) { - setState(() { - _isLoading = false; - }); - } - } - } + // 3. Show success message. + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('TPIN changed successfully!'), + backgroundColor: Colors.green, + ), + ); + // 4. Navigate back to the profile screen or home. + Navigator.of(context).popUntil((route) => route.isFirst); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('An error occurred: $e'), + backgroundColor: Colors.red, + ), + ); + } + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + } - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Verify OTP'), - ), - body: SingleChildScrollView( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: 24), - const Text( - 'Enter the OTP sent to your registered mobile number.', - textAlign: TextAlign.center, - style: TextStyle(fontSize: 16), - ), - const SizedBox(height: 32), - PinInputField( - controller: _otpController, - ), - const SizedBox(height: 32), - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: _isLoading ? null : _handleVerifyOtp, - child: _isLoading - ? const SizedBox( - height: 24, - width: 24, - child: CircularProgressIndicator( - color: Colors.white, - strokeWidth: 2.5, - ), - ) - : const Text('Verify & Change TPIN'), - ), - ), - ], - ), - ), - ); - } - } \ No newline at end of file + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Verify OTP'), + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 24), + const Text( + 'Enter the OTP sent to your registered mobile number.', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16), + ), + const SizedBox(height: 32), + PinInputField( + controller: _otpController, + ), + const SizedBox(height: 32), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _isLoading ? null : _handleVerifyOtp, + child: _isLoading + ? const SizedBox( + height: 24, + width: 24, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2.5, + ), + ) + : const Text('Verify & Change TPIN'), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/profile/tpin/change_tpin_screen.dart b/lib/features/profile/tpin/change_tpin_screen.dart index 850d361..1c29247 100644 --- a/lib/features/profile/tpin/change_tpin_screen.dart +++ b/lib/features/profile/tpin/change_tpin_screen.dart @@ -36,7 +36,8 @@ class _ChangeTpinScreenState extends State { }); try { // 1. Get OTP for TPIN change. - await _changePasswordService.getOtpTpin(mobileNumber: widget.mobileNumber); + await _changePasswordService.getOtpTpin( + mobileNumber: widget.mobileNumber); // 2. Navigate to the OTP screen on success. if (mounted) { @@ -143,4 +144,4 @@ class _ChangeTpinScreenState extends State { ), ); } -} \ No newline at end of file +} diff --git a/lib/features/quick_pay/screens/quick_pay_outside_bank_screen.dart b/lib/features/quick_pay/screens/quick_pay_outside_bank_screen.dart index 81bdc94..32eb354 100644 --- a/lib/features/quick_pay/screens/quick_pay_outside_bank_screen.dart +++ b/lib/features/quick_pay/screens/quick_pay_outside_bank_screen.dart @@ -31,9 +31,9 @@ class QuickPayOutsideBankScreen extends StatefulWidget { class _QuickPayOutsideBankScreen extends State { final _formKey = GlobalKey(); final _limitService = getIt(); -Limit? _limit; -bool _isLoadingLimit = true; -final _formatCurrency = NumberFormat.currency(locale: 'en_IN', symbol: '₹'); + Limit? _limit; + bool _isLoadingLimit = true; + final _formatCurrency = NumberFormat.currency(locale: 'en_IN', symbol: '₹'); // Controllers final accountNumberController = TextEditingController(); final confirmAccountNumberController = TextEditingController(); @@ -56,7 +56,7 @@ final _formatCurrency = NumberFormat.currency(locale: 'en_IN', symbol: '₹'); @override void initState() { super.initState(); - _loadLimit(); + _loadLimit(); _ifscFocusNode.addListener(() { if (!_ifscFocusNode.hasFocus && ifscController.text.trim().length == 11) { _validateIFSC(); @@ -70,47 +70,48 @@ final _formatCurrency = NumberFormat.currency(locale: 'en_IN', symbol: '₹'); amountController.addListener(_checkAmountLimit); } -Future _loadLimit() async { - setState(() { - _isLoadingLimit = true; - }); - try { - final limitData = await _limitService.getLimit(); + Future _loadLimit() async { setState(() { - _limit = limitData; - _isLoadingLimit = false; - }); - } catch (e) { - // Handle error if needed - setState(() { - _isLoadingLimit = false; + _isLoadingLimit = true; }); + try { + final limitData = await _limitService.getLimit(); + setState(() { + _limit = limitData; + _isLoadingLimit = false; + }); + } catch (e) { + // Handle error if needed + setState(() { + _isLoadingLimit = false; + }); + } } -} // Add this method to check the amount against the limit - void _checkAmountLimit() { - if (_limit == null) return; + void _checkAmountLimit() { + if (_limit == null) return; - final amount = double.tryParse(amountController.text) ?? 0; - final remainingLimit = _limit!.dailyLimit - _limit!.usedLimit; - final bool isOverLimit = amount > remainingLimit; + final amount = double.tryParse(amountController.text) ?? 0; + final remainingLimit = _limit!.dailyLimit - _limit!.usedLimit; + final bool isOverLimit = amount > remainingLimit; - if (isOverLimit) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Amount exceeds remaining daily limit of ${_formatCurrency.format(remainingLimit)}'), - backgroundColor: Colors.red, - ), - ); - } + if (isOverLimit) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Amount exceeds remaining daily limit of ${_formatCurrency.format(remainingLimit)}'), + backgroundColor: Colors.red, + ), + ); + } - if (_isAmountOverLimit != isOverLimit) { - setState(() { - _isAmountOverLimit = isOverLimit; - }); - } - } + if (_isAmountOverLimit != isOverLimit) { + setState(() { + _isAmountOverLimit = isOverLimit; + }); + } + } void _validateIFSC() async { final ifsc = ifscController.text.trim().toUpperCase(); @@ -769,89 +770,92 @@ Future _loadLimit() async { ), const SizedBox(height: 25), Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - child: TextFormField( - controller: phoneController, - keyboardType: TextInputType.phone, - decoration: InputDecoration( - labelText: AppLocalizations.of(context).phone, - prefixIcon: const Icon(Icons.phone), - border: const OutlineInputBorder(), - isDense: true, - filled: true, - fillColor: Theme.of(context).scaffoldBackgroundColor, - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Theme.of(context).colorScheme.outline), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Theme.of(context).colorScheme.primary, - width: 2), + Row( + children: [ + Expanded( + child: TextFormField( + controller: phoneController, + keyboardType: TextInputType.phone, + decoration: InputDecoration( + labelText: AppLocalizations.of(context).phone, + prefixIcon: const Icon(Icons.phone), + border: const OutlineInputBorder(), + isDense: true, + filled: true, + fillColor: + Theme.of(context).scaffoldBackgroundColor, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.outline), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 2), + ), + ), + textInputAction: TextInputAction.next, + validator: (value) => value == null || value.isEmpty + ? AppLocalizations.of(context).phoneRequired + : null, ), ), - textInputAction: TextInputAction.next, - validator: (value) => value == null || value.isEmpty - ? AppLocalizations.of(context).phoneRequired - : null, - ), - ), - const SizedBox(width: 10), - Expanded( - child: TextFormField( - decoration: InputDecoration( - labelText: AppLocalizations.of(context).amount, - border: const OutlineInputBorder(), - isDense: true, - filled: true, - fillColor: Theme.of(context).scaffoldBackgroundColor, - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Theme.of(context).colorScheme.outline), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Theme.of(context).colorScheme.primary, - width: 2), + const SizedBox(width: 10), + Expanded( + child: TextFormField( + decoration: InputDecoration( + labelText: AppLocalizations.of(context).amount, + border: const OutlineInputBorder(), + isDense: true, + filled: true, + fillColor: + Theme.of(context).scaffoldBackgroundColor, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.outline), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 2), + ), + ), + controller: amountController, + keyboardType: TextInputType.number, + textInputAction: TextInputAction.next, + validator: (value) { + if (value == null || value.isEmpty) { + return AppLocalizations.of(context) + .amountRequired; + } + final amount = double.tryParse(value); + if (amount == null || amount <= 0) { + return AppLocalizations.of(context).validAmount; + } + return null; + }, ), ), - controller: amountController, - keyboardType: TextInputType.number, - textInputAction: TextInputAction.next, - validator: (value) { - if (value == null || value.isEmpty) { - return AppLocalizations.of(context).amountRequired; - } - final amount = double.tryParse(value); - if (amount == null || amount <= 0) { - return AppLocalizations.of(context).validAmount; - } - return null; - }, - ), + ], ), ], - ), - ], ), const SizedBox(height: 8), -if (_isLoadingLimit) - const Padding( - padding: EdgeInsets.only(left: 8.0), - child: Text('Fetching daily limit...'), - ), -if (!_isLoadingLimit && _limit != null) - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: Text( - 'Remaining Daily Limit: ${_formatCurrency.format(_limit!.dailyLimit - _limit!.usedLimit)}', - style: Theme.of(context).textTheme.bodySmall, - ), - ), + if (_isLoadingLimit) + const Padding( + padding: EdgeInsets.only(left: 8.0), + child: Text('Fetching daily limit...'), + ), + if (!_isLoadingLimit && _limit != null) + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Text( + 'Remaining Daily Limit: ${_formatCurrency.format(_limit!.dailyLimit - _limit!.usedLimit)}', + style: Theme.of(context).textTheme.bodySmall, + ), + ), const SizedBox(height: 30), Row( children: [ @@ -864,31 +868,34 @@ if (!_isLoadingLimit && _limit != null) ], ), const SizedBox(height: 45), - Align( - alignment: Alignment.center, - child: SwipeButton.expand( - thumb: Icon(Icons.arrow_forward, - color: _isAmountOverLimit ? Colors.grey : Theme.of(context).dialogBackgroundColor), - activeThumbColor: _isAmountOverLimit ? Colors.grey.shade700 : - Theme.of(context).colorScheme.primary, - activeTrackColor: _isAmountOverLimit - ? Colors.grey.shade300 - : Theme.of(context).colorScheme.secondary.withAlpha(100), - borderRadius: BorderRadius.circular(30), - height: 56, - onSwipe: () { - if (_isAmountOverLimit) { - return; // Do nothing if amount is over the limit - } - _onProceedToPay(); - }, - child: Text( - AppLocalizations.of(context).swipeToPay, - style: const TextStyle( - fontSize: 16, fontWeight: FontWeight.bold), - ), - ), - ), + Align( + alignment: Alignment.center, + child: SwipeButton.expand( + thumb: Icon(Icons.arrow_forward, + color: _isAmountOverLimit + ? Colors.grey + : Theme.of(context).dialogBackgroundColor), + activeThumbColor: _isAmountOverLimit + ? Colors.grey.shade700 + : Theme.of(context).colorScheme.primary, + activeTrackColor: _isAmountOverLimit + ? Colors.grey.shade300 + : Theme.of(context).colorScheme.secondary.withAlpha(100), + borderRadius: BorderRadius.circular(30), + height: 56, + onSwipe: () { + if (_isAmountOverLimit) { + return; // Do nothing if amount is over the limit + } + _onProceedToPay(); + }, + child: Text( + AppLocalizations.of(context).swipeToPay, + style: const TextStyle( + fontSize: 16, fontWeight: FontWeight.bold), + ), + ), + ), ], ), ), diff --git a/lib/features/quick_pay/screens/quick_pay_within_bank_screen.dart b/lib/features/quick_pay/screens/quick_pay_within_bank_screen.dart index 11982c9..1210409 100644 --- a/lib/features/quick_pay/screens/quick_pay_within_bank_screen.dart +++ b/lib/features/quick_pay/screens/quick_pay_within_bank_screen.dart @@ -22,9 +22,9 @@ class QuickPayWithinBankScreen extends StatefulWidget { class _QuickPayWithinBankScreen extends State { final _formKey = GlobalKey(); final _limitService = getIt(); -Limit? _limit; -bool _isLoadingLimit = true; -final _formatCurrency = NumberFormat.currency(locale: 'en_IN', symbol: '₹'); + Limit? _limit; + bool _isLoadingLimit = true; + final _formatCurrency = NumberFormat.currency(locale: 'en_IN', symbol: '₹'); final TextEditingController accountNumberController = TextEditingController(); final TextEditingController confirmAccountNumberController = TextEditingController(); @@ -46,47 +46,48 @@ final _formatCurrency = NumberFormat.currency(locale: 'en_IN', symbol: '₹'); amountController.addListener(_checkAmountLimit); } -Future _loadLimit() async { - setState(() { - _isLoadingLimit = true; - }); - try { - final limitData = await _limitService.getLimit(); + Future _loadLimit() async { setState(() { - _limit = limitData; - _isLoadingLimit = false; + _isLoadingLimit = true; }); - } catch (e) { - // Handle error if needed - setState(() { - _isLoadingLimit = false; - }); - } -} - -void _checkAmountLimit() { - if (_limit == null) return; - - final amount = double.tryParse(amountController.text) ?? 0; - final remainingLimit = _limit!.dailyLimit - _limit!.usedLimit; - final bool isOverLimit = amount > remainingLimit; - - if (isOverLimit) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Amount exceeds remaining daily limit of ${_formatCurrency.format(remainingLimit)}'), - backgroundColor: Colors.red, - ), - ); + try { + final limitData = await _limitService.getLimit(); + setState(() { + _limit = limitData; + _isLoadingLimit = false; + }); + } catch (e) { + // Handle error if needed + setState(() { + _isLoadingLimit = false; + }); + } } - // Update state only if it changes to avoid unnecessary rebuilds - if (_isAmountOverLimit != isOverLimit) { - setState(() { - _isAmountOverLimit = isOverLimit; - }); + void _checkAmountLimit() { + if (_limit == null) return; + + final amount = double.tryParse(amountController.text) ?? 0; + final remainingLimit = _limit!.dailyLimit - _limit!.usedLimit; + final bool isOverLimit = amount > remainingLimit; + + if (isOverLimit) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Amount exceeds remaining daily limit of ${_formatCurrency.format(remainingLimit)}'), + backgroundColor: Colors.red, + ), + ); + } + + // Update state only if it changes to avoid unnecessary rebuilds + if (_isAmountOverLimit != isOverLimit) { + setState(() { + _isAmountOverLimit = isOverLimit; + }); + } } -} void _resetBeneficiaryValidation() { if (_isBeneficiaryValidated || @@ -153,306 +154,314 @@ void _checkAmountLimit() { child: Form( key: _formKey, child: SingleChildScrollView( - child: Column( - children: [ - const SizedBox(height: 10), - TextFormField( - decoration: InputDecoration( - labelText: AppLocalizations.of(context).debitAccountNumber, - border: const OutlineInputBorder(), - isDense: true, - filled: true, - fillColor: Theme.of(context).scaffoldBackgroundColor, + child: Column( + children: [ + const SizedBox(height: 10), + TextFormField( + decoration: InputDecoration( + labelText: AppLocalizations.of(context).debitAccountNumber, + border: const OutlineInputBorder(), + isDense: true, + filled: true, + fillColor: Theme.of(context).scaffoldBackgroundColor, + ), + readOnly: true, + controller: TextEditingController(text: widget.debitAccount), + keyboardType: TextInputType.number, + textInputAction: TextInputAction.next, + enabled: false, ), - readOnly: true, - controller: TextEditingController(text: widget.debitAccount), - keyboardType: TextInputType.number, - textInputAction: TextInputAction.next, - enabled: false, - ), - const SizedBox(height: 20), - TextFormField( - decoration: InputDecoration( - labelText: AppLocalizations.of(context).accountNumber, - border: const OutlineInputBorder(), - isDense: true, - filled: true, - fillColor: Theme.of(context).scaffoldBackgroundColor, - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Theme.of(context).colorScheme.outline), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Theme.of(context).colorScheme.primary, width: 2), + const SizedBox(height: 20), + TextFormField( + decoration: InputDecoration( + labelText: AppLocalizations.of(context).accountNumber, + border: const OutlineInputBorder(), + isDense: true, + filled: true, + fillColor: Theme.of(context).scaffoldBackgroundColor, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.outline), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 2), + ), ), + controller: accountNumberController, + keyboardType: TextInputType.number, + obscureText: true, + textInputAction: TextInputAction.next, + validator: (value) { + if (value == null || value.isEmpty) { + return AppLocalizations.of(context).accountNumberRequired; + } else if (value.length != 11) { + return AppLocalizations.of(context).validAccountNumber; + } + return null; + }, ), - controller: accountNumberController, - keyboardType: TextInputType.number, - obscureText: true, - textInputAction: TextInputAction.next, - validator: (value) { - if (value == null || value.isEmpty) { - return AppLocalizations.of(context).accountNumberRequired; - } else if (value.length != 11) { - return AppLocalizations.of(context).validAccountNumber; - } - return null; - }, - ), - const SizedBox(height: 25), - TextFormField( - controller: confirmAccountNumberController, - decoration: InputDecoration( - labelText: AppLocalizations.of(context).confirmAccountNumber, - // prefixIcon: Icon(Icons.person), - border: const OutlineInputBorder(), - isDense: true, - filled: true, - fillColor: Theme.of(context).scaffoldBackgroundColor, - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Theme.of(context).colorScheme.outline), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Theme.of(context).colorScheme.primary, width: 2), + const SizedBox(height: 25), + TextFormField( + controller: confirmAccountNumberController, + decoration: InputDecoration( + labelText: + AppLocalizations.of(context).confirmAccountNumber, + // prefixIcon: Icon(Icons.person), + border: const OutlineInputBorder(), + isDense: true, + filled: true, + fillColor: Theme.of(context).scaffoldBackgroundColor, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.outline), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 2), + ), ), + keyboardType: TextInputType.number, + textInputAction: TextInputAction.next, + validator: (value) { + if (value == null || value.isEmpty) { + return AppLocalizations.of(context).reenterAccountNumber; + } + if (value != accountNumberController.text) { + return AppLocalizations.of(context).accountMismatch; + } + return null; + }, ), - keyboardType: TextInputType.number, - textInputAction: TextInputAction.next, - validator: (value) { - if (value == null || value.isEmpty) { - return AppLocalizations.of(context).reenterAccountNumber; - } - if (value != accountNumberController.text) { - return AppLocalizations.of(context).accountMismatch; - } - return null; - }, - ), - if (!_isBeneficiaryValidated) - Padding( - padding: const EdgeInsets.only(top: 12.0), - child: SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: _isValidating - ? null - : () { - if (accountNumberController.text.length == 11 && - confirmAccountNumberController.text == - accountNumberController.text) { - _validateBeneficiary(); - } else { - setState(() { - _validationError = - AppLocalizations.of(context) - .accountMismatch; - }); - } - }, - child: _isValidating - ? const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator(strokeWidth: 2), - ) - : Text( - AppLocalizations.of(context).validateBeneficiary), + if (!_isBeneficiaryValidated) + Padding( + padding: const EdgeInsets.only(top: 12.0), + child: SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _isValidating + ? null + : () { + if (accountNumberController.text.length == 11 && + confirmAccountNumberController.text == + accountNumberController.text) { + _validateBeneficiary(); + } else { + setState(() { + _validationError = + AppLocalizations.of(context) + .accountMismatch; + }); + } + }, + child: _isValidating + ? const SizedBox( + width: 20, + height: 20, + child: + CircularProgressIndicator(strokeWidth: 2), + ) + : Text(AppLocalizations.of(context) + .validateBeneficiary), + ), + ), + ), + if (_beneficiaryName != null && _isBeneficiaryValidated) + Padding( + padding: const EdgeInsets.only(top: 12.0), + child: Row( + children: [ + const Icon(Icons.check_circle, color: Colors.green), + const SizedBox(width: 8), + Text( + '${AppLocalizations.of(context).beneficiaryName}: $_beneficiaryName', + style: const TextStyle( + color: Colors.green, fontWeight: FontWeight.bold), + ), + ], + ), + ), + if (_validationError != null) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + _validationError!, + style: const TextStyle(color: Colors.red), + ), + ), + const SizedBox(height: 24), + DropdownButtonFormField( + decoration: InputDecoration( + labelText: AppLocalizations.of( + context, + ).beneficiaryAccountType, + border: const OutlineInputBorder(), + isDense: true, + filled: true, + fillColor: Theme.of(context).scaffoldBackgroundColor, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.outline), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 2), + ), + ), + value: _selectedAccountType, + items: [ + DropdownMenuItem( + value: 'SB', + child: Text(AppLocalizations.of(context).savings), + ), + DropdownMenuItem( + value: 'LN', + child: Text(AppLocalizations.of(context).loan), + ), + ], + onChanged: (value) { + setState(() { + _selectedAccountType = value; + }); + }, + validator: (value) { + if (value == null || value.isEmpty) { + return AppLocalizations.of(context).selectAccountType; + } + return null; + }, + ), + const SizedBox(height: 25), + TextFormField( + controller: remarksController, + decoration: InputDecoration( + labelText: AppLocalizations.of(context).remarks, + border: const OutlineInputBorder(), + isDense: true, + filled: true, + fillColor: Theme.of(context).scaffoldBackgroundColor, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.outline), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 2), ), ), ), - if (_beneficiaryName != null && _isBeneficiaryValidated) - Padding( - padding: const EdgeInsets.only(top: 12.0), - child: Row( - children: [ - const Icon(Icons.check_circle, color: Colors.green), - const SizedBox(width: 8), - Text( - '${AppLocalizations.of(context).beneficiaryName}: $_beneficiaryName', - style: const TextStyle( - color: Colors.green, fontWeight: FontWeight.bold), - ), - ], + const SizedBox(height: 25), + TextFormField( + decoration: InputDecoration( + labelText: AppLocalizations.of(context).amount, + border: const OutlineInputBorder(), + isDense: true, + filled: true, + fillColor: Theme.of(context).scaffoldBackgroundColor, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.outline), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 2), + ), ), + controller: amountController, + keyboardType: TextInputType.number, + textInputAction: TextInputAction.next, + validator: (value) { + if (value == null || value.isEmpty) { + return AppLocalizations.of(context).amountRequired; + } + final amount = double.tryParse(value); + if (amount == null || amount <= 0) { + return AppLocalizations.of(context).validAmount; + } + return null; + }, ), - if (_validationError != null) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - _validationError!, - style: const TextStyle(color: Colors.red), + const SizedBox(height: 8), + if (_isLoadingLimit) const Text('Fetching daily limit...'), + if (!_isLoadingLimit && _limit != null) + Text( + 'Remaining Daily Limit: ${_formatCurrency.format(_limit!.dailyLimit - _limit!.usedLimit)}', + style: Theme.of(context).textTheme.bodySmall, ), - ), - const SizedBox(height: 24), - DropdownButtonFormField( - decoration: InputDecoration( - labelText: AppLocalizations.of( - context, - ).beneficiaryAccountType, - border: const OutlineInputBorder(), - isDense: true, - filled: true, - fillColor: Theme.of(context).scaffoldBackgroundColor, - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Theme.of(context).colorScheme.outline), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Theme.of(context).colorScheme.primary, width: 2), - ), - ), - value: _selectedAccountType, - items: [ - DropdownMenuItem( - value: 'SB', - child: Text(AppLocalizations.of(context).savings), - ), - DropdownMenuItem( - value: 'LN', - child: Text(AppLocalizations.of(context).loan), - ), - ], - onChanged: (value) { - setState(() { - _selectedAccountType = value; - }); - }, - validator: (value) { - if (value == null || value.isEmpty) { - return AppLocalizations.of(context).selectAccountType; - } - return null; - }, - ), - const SizedBox(height: 25), - TextFormField( - controller: remarksController, - decoration: InputDecoration( - labelText: AppLocalizations.of(context).remarks, - border: const OutlineInputBorder(), - isDense: true, - filled: true, - fillColor: Theme.of(context).scaffoldBackgroundColor, - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Theme.of(context).colorScheme.outline), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Theme.of(context).colorScheme.primary, width: 2), - ), - ), - ), - const SizedBox(height: 25), - - TextFormField( - decoration: InputDecoration( - labelText: AppLocalizations.of(context).amount, - border: const OutlineInputBorder(), - isDense: true, - filled: true, - fillColor: Theme.of(context).scaffoldBackgroundColor, - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Theme.of(context).colorScheme.outline), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Theme.of(context).colorScheme.primary, width: 2), - ), - ), - controller: amountController, - keyboardType: TextInputType.number, - textInputAction: TextInputAction.next, - validator: (value) { - if (value == null || value.isEmpty) { - return AppLocalizations.of(context).amountRequired; - } - final amount = double.tryParse(value); - if (amount == null || amount <= 0) { - return AppLocalizations.of(context).validAmount; - } - return null; - }, - ), - const SizedBox(height: 8), -if (_isLoadingLimit) - const Text('Fetching daily limit...'), -if (!_isLoadingLimit && _limit != null) - Text( - 'Remaining Daily Limit: ${_formatCurrency.format(_limit!.dailyLimit - _limit!.usedLimit)}', - style: Theme.of(context).textTheme.bodySmall, - ), - const SizedBox(height: 45), - Align( - alignment: Alignment.center, - child: SwipeButton.expand( - thumb: Icon(Icons.arrow_forward, - color: _isAmountOverLimit ? Colors.grey : Theme.of(context).dialogBackgroundColor), - activeThumbColor: _isAmountOverLimit ? Colors.grey.shade700 : - Theme.of(context).colorScheme.primary, - activeTrackColor: _isAmountOverLimit - ? Colors.grey.shade300 - : Theme.of( - context, - ).colorScheme.secondary.withAlpha(100), - borderRadius: BorderRadius.circular(30), - height: 56, - child: Text( - AppLocalizations.of(context).swipeToPay, - style: const TextStyle(fontSize: 16), - ), - onSwipe: () { - if (_isAmountOverLimit) { - return; // Do nothing if amount is over limit - } - if (_formKey.currentState!.validate()) { - if (!_isBeneficiaryValidated) { - setState(() { - _validationError = AppLocalizations.of(context) - .validateBeneficiaryproceeding; - }); - return; - } - // Perform payment logic - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => TransactionPinScreen( - onPinCompleted: (pinScreenContext, tpin) async { - final transfer = Transfer( - fromAccount: widget.debitAccount, - toAccount: accountNumberController.text, - toAccountType: _selectedAccountType!, - amount: amountController.text, - tpin: tpin, - remarks: remarksController.text, - ); + const SizedBox(height: 45), + Align( + alignment: Alignment.center, + child: SwipeButton.expand( + thumb: Icon(Icons.arrow_forward, + color: _isAmountOverLimit + ? Colors.grey + : Theme.of(context).dialogBackgroundColor), + activeThumbColor: _isAmountOverLimit + ? Colors.grey.shade700 + : Theme.of(context).colorScheme.primary, + activeTrackColor: _isAmountOverLimit + ? Colors.grey.shade300 + : Theme.of( + context, + ).colorScheme.secondary.withAlpha(100), + borderRadius: BorderRadius.circular(30), + height: 56, + child: Text( + AppLocalizations.of(context).swipeToPay, + style: const TextStyle(fontSize: 16), + ), + onSwipe: () { + if (_isAmountOverLimit) { + return; // Do nothing if amount is over limit + } + if (_formKey.currentState!.validate()) { + if (!_isBeneficiaryValidated) { + setState(() { + _validationError = AppLocalizations.of(context) + .validateBeneficiaryproceeding; + }); + return; + } + // Perform payment logic + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TransactionPinScreen( + onPinCompleted: (pinScreenContext, tpin) async { + final transfer = Transfer( + fromAccount: widget.debitAccount, + toAccount: accountNumberController.text, + toAccountType: _selectedAccountType!, + amount: amountController.text, + tpin: tpin, + remarks: remarksController.text, + ); - final paymentService = getIt(); - final paymentResponseFuture = paymentService - .processQuickPayWithinBank(transfer); + final paymentService = getIt(); + final paymentResponseFuture = paymentService + .processQuickPayWithinBank(transfer); - Navigator.of(pinScreenContext).pushReplacement( - MaterialPageRoute( - builder: (_) => PaymentAnimationScreen( - paymentResponse: paymentResponseFuture), - ), - ); - }, - ), - ), - ); - } - }, - ), - ), - ], - ), + Navigator.of(pinScreenContext).pushReplacement( + MaterialPageRoute( + builder: (_) => PaymentAnimationScreen( + paymentResponse: paymentResponseFuture), + ), + ); + }, + ), + ), + ); + } + }, + ), + ), + ], + ), ), ), ), diff --git a/lib/main.dart b/lib/main.dart index a4e7953..c6920a2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,13 +15,13 @@ void main() async { ]); // Check for device compromise - // final compromisedMessage = await SecurityService.deviceCompromisedMessage; - // if (compromisedMessage != null) { - // runApp(MaterialApp( - // home: SecurityErrorScreen(message: compromisedMessage), - // )); - // return; - // } + final compromisedMessage = await SecurityService.deviceCompromisedMessage; + if (compromisedMessage != null) { + runApp(MaterialApp( + home: SecurityErrorScreen(message: compromisedMessage), + )); + return; + } await setupDependencies(); runApp(const KMobile()); } diff --git a/lib/widgets/pin_input_field.dart b/lib/widgets/pin_input_field.dart index f7016d3..cfd1597 100644 --- a/lib/widgets/pin_input_field.dart +++ b/lib/widgets/pin_input_field.dart @@ -55,4 +55,4 @@ class PinInputField extends StatelessWidget { }, ); } -} \ No newline at end of file +} diff --git a/lib/widgets/tnc_dialog.dart b/lib/widgets/tnc_dialog.dart index 9584466..8acf292 100644 --- a/lib/widgets/tnc_dialog.dart +++ b/lib/widgets/tnc_dialog.dart @@ -1,21 +1,21 @@ - import 'package:flutter/material.dart'; +import 'package:flutter/material.dart'; - class TncDialog extends StatefulWidget { - final Future Function() onProceed; +class TncDialog extends StatefulWidget { + final Future Function() onProceed; - const TncDialog({Key? key, required this.onProceed}) : super(key: key); + const TncDialog({Key? key, required this.onProceed}) : super(key: key); - @override - _TncDialogState createState() => _TncDialogState(); - } + @override + _TncDialogState createState() => _TncDialogState(); +} - class _TncDialogState extends State { - bool _isAgreed = false; - bool _isLoading = false; - // --- NEW: ScrollController for the TNC text --- - final ScrollController _scrollController = ScrollController(); +class _TncDialogState extends State { + bool _isAgreed = false; + bool _isLoading = false; + // --- NEW: ScrollController for the TNC text --- + final ScrollController _scrollController = ScrollController(); - final String _termsAndConditionsText = """ + final String _termsAndConditionsText = """ Effective Date: November 10, 2025 These Terms and Conditions ("Terms") govern your access to and use of The Bank mobile banking application (the "App") and the services @@ -111,101 +111,101 @@ access to or use of the App and Services. """; - void _handleProceed() async { - if (_isLoading) return; + void _handleProceed() async { + if (_isLoading) return; - setState(() { - _isLoading = true; - }); + setState(() { + _isLoading = true; + }); - await widget.onProceed(); + await widget.onProceed(); - if (mounted) { - setState(() { - _isLoading = false; - }); - } - } + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } - @override - void dispose() { - _scrollController.dispose(); // --- NEW: Dispose the ScrollController --- - super.dispose(); - } + @override + void dispose() { + _scrollController.dispose(); // --- NEW: Dispose the ScrollController --- + super.dispose(); + } - @override - Widget build(BuildContext context) { - final screenSize = MediaQuery.of(context).size; + @override + Widget build(BuildContext context) { + final screenSize = MediaQuery.of(context).size; - return AlertDialog( - title: const Text('Terms and Conditions'), - content: SizedBox( - height: screenSize.height * 0.5, // 50% of screen height - width: screenSize.width * 0.9, // 90% of screen width - // --- MODIFIED: Use a Column to separate scrollable text from fixed checkbox --- - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // --- NEW: Expanded Scrollbar for the TNC text --- - Expanded( - child: Scrollbar( - controller: _scrollController, - thumbVisibility: true, // Always show the scrollbar thumb - // To place the scrollbar on the left, you might need to wrap - // this in a Directionality widget or use a custom scrollbar. - // For now, it will appear on the right as is standard. - child: SingleChildScrollView( - controller: _scrollController, - child: _isLoading - ? const Center( - child: Padding( - padding: EdgeInsets.all(16.0), - child: CircularProgressIndicator(), - ), - ) - : Text(_termsAndConditionsText), - ), - ), - ), - const SizedBox(height: 16), // Space between text and checkbox - // --- MODIFIED: Checkbox Row is now outside the SingleChildScrollView --- - Row( - children: [ - Checkbox( - value: _isAgreed, - onChanged: (bool? value) { - setState(() { - _isAgreed = value ?? false; - }); - }, - ), - const Flexible( - child: Text('I agree to the Terms and Conditions')), - ], - ), - ], - ), - ), - actions: [ - TextButton( - onPressed: _isLoading - ? null - : () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'You must agree to the terms and conditions to proceed.'), - behavior: SnackBarBehavior.floating, - ), - ); - }, - child: const Text('Disagree'), - ), - ElevatedButton( - onPressed: _isAgreed && !_isLoading ? _handleProceed : null, - child: const Text('Proceed'), - ), - ], - ); - } - } \ No newline at end of file + return AlertDialog( + title: const Text('Terms and Conditions'), + content: SizedBox( + height: screenSize.height * 0.5, // 50% of screen height + width: screenSize.width * 0.9, // 90% of screen width + // --- MODIFIED: Use a Column to separate scrollable text from fixed checkbox --- + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // --- NEW: Expanded Scrollbar for the TNC text --- + Expanded( + child: Scrollbar( + controller: _scrollController, + thumbVisibility: true, // Always show the scrollbar thumb + // To place the scrollbar on the left, you might need to wrap + // this in a Directionality widget or use a custom scrollbar. + // For now, it will appear on the right as is standard. + child: SingleChildScrollView( + controller: _scrollController, + child: _isLoading + ? const Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: CircularProgressIndicator(), + ), + ) + : Text(_termsAndConditionsText), + ), + ), + ), + const SizedBox(height: 16), // Space between text and checkbox + // --- MODIFIED: Checkbox Row is now outside the SingleChildScrollView --- + Row( + children: [ + Checkbox( + value: _isAgreed, + onChanged: (bool? value) { + setState(() { + _isAgreed = value ?? false; + }); + }, + ), + const Flexible( + child: Text('I agree to the Terms and Conditions')), + ], + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: _isLoading + ? null + : () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'You must agree to the terms and conditions to proceed.'), + behavior: SnackBarBehavior.floating, + ), + ); + }, + child: const Text('Disagree'), + ), + ElevatedButton( + onPressed: _isAgreed && !_isLoading ? _handleProceed : null, + child: const Text('Proceed'), + ), + ], + ); + } +}