From 32e8b85ceea330ba146f87d775f99233be9c9303 Mon Sep 17 00:00:00 2001 From: Nilanjan Chakrabarti Date: Thu, 30 Oct 2025 12:23:56 +0530 Subject: [PATCH] APK #1 --- lib/api/services/limit_service.dart | 57 +++++ lib/api/services/user_service.dart | 2 +- lib/di/injection.dart | 5 +- .../profile/daily_transaction_limit.dart | 202 ++++++++++++++++++ lib/features/profile/profile_screen.dart | 12 ++ .../screens/daily_transaction_limit.dart | 161 -------------- .../service/screens/service_screen.dart | 14 +- 7 files changed, 277 insertions(+), 176 deletions(-) create mode 100644 lib/api/services/limit_service.dart create mode 100644 lib/features/profile/daily_transaction_limit.dart delete mode 100644 lib/features/service/screens/daily_transaction_limit.dart diff --git a/lib/api/services/limit_service.dart b/lib/api/services/limit_service.dart new file mode 100644 index 0000000..5ad3b6f --- /dev/null +++ b/lib/api/services/limit_service.dart @@ -0,0 +1,57 @@ +// ignore_for_file: collection_methods_unrelated_type +import 'dart:developer'; +import 'package:dio/dio.dart'; + +class Limit { + final double dailyLimit; + final double usedLimit; + + Limit({ + required this.dailyLimit, + required this.usedLimit, + }); + + factory Limit.fromJson(Map json) { + return Limit( + dailyLimit: json['dailyLimit']!, + usedLimit: json['usedLimit']!, + ); + } +} + +class LimitService { + final Dio _dio; + LimitService(this._dio); + + Future getLimit() async { + try { + final response = await _dio.get('/api/customer/daily-limit'); + if (response.statusCode == 200) { + log('Response: ${response.data}'); + return Limit.fromJson(response.data); + } else { + throw Exception('Failed to load'); + } + } on DioException catch (e) { + throw Exception('Network error: ${e.message}'); + } catch (e) { + throw Exception('Unexpected error: ${e.toString()}'); + } + } + + void editLimit( double newLimit) async { + try { + final response = await _dio.post('/api/customer/daily-limit', + data: '{"amount": $newLimit}'); + if (response.statusCode == 200) { + log('Response: ${response.data}'); + } else { + throw Exception('Failed to load'); + } + } on DioException catch (e) { + throw Exception('Network error: ${e.message}'); + } catch (e) { + throw Exception('Unexpected error: ${e.toString()}'); + } + } +} diff --git a/lib/api/services/user_service.dart b/lib/api/services/user_service.dart index 89fd614..3c5bde5 100644 --- a/lib/api/services/user_service.dart +++ b/lib/api/services/user_service.dart @@ -10,7 +10,7 @@ class UserService { Future> getUserDetails() async { try { - final response = await _dio.get('/api/customer/details'); + final response = await _dio.get('/api/customer'); if (response.statusCode == 200) { log('Response: ${response.data}'); return (response.data as List) diff --git a/lib/di/injection.dart b/lib/di/injection.dart index 465e5b2..bef663c 100644 --- a/lib/di/injection.dart +++ b/lib/di/injection.dart @@ -1,3 +1,4 @@ +import 'package:kmobile/api/services/limit_service.dart'; import 'package:kmobile/api/services/rtgs_service.dart'; import 'package:kmobile/api/services/neft_service.dart'; import 'package:kmobile/api/services/imps_service.dart'; @@ -46,6 +47,7 @@ Future setupDependencies() async { getIt.registerSingleton(PaymentService(getIt())); getIt.registerSingleton(BeneficiaryService(getIt())); + getIt.registerSingleton(LimitService(getIt())); getIt.registerSingleton(NeftService(getIt())); getIt.registerSingleton(RtgsService(getIt())); getIt.registerSingleton(ImpsService(getIt())); @@ -69,12 +71,13 @@ Dio _createDioClient() { baseUrl: 'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com:8080', //test //'http://lb-kccb-mobile-banking-app-848675342.ap-south-1.elb.amazonaws.com', //prod - //'https://kccbmbnk.net', + //'https://kccbmbnk.net', //prod small connectTimeout: const Duration(seconds: 60), receiveTimeout: const Duration(seconds: 60), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', + 'X-Login-Type': 'MB', }, ), ); diff --git a/lib/features/profile/daily_transaction_limit.dart b/lib/features/profile/daily_transaction_limit.dart new file mode 100644 index 0000000..36a5c19 --- /dev/null +++ b/lib/features/profile/daily_transaction_limit.dart @@ -0,0 +1,202 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:kmobile/api/services/limit_service.dart'; +import 'package:kmobile/app.dart'; +import 'package:kmobile/di/injection.dart'; +import 'package:kmobile/l10n/app_localizations.dart'; +import 'package:intl/intl.dart'; + +class DailyLimitScreen extends StatefulWidget { + const DailyLimitScreen({super.key}); + @override + State createState() => _DailyLimitScreenState(); +} + +class _DailyLimitScreenState extends State { + double? _currentLimit; + double? _spentAmount = 0.0; + final _limitController = TextEditingController(); + var service = getIt(); + Limit? limit; + + @override + void initState() { + super.initState(); + _loadlimits(); + } + + Future _loadlimits() async { + final limit_data = await service.getLimit(); + setState(() { + limit = limit_data; + }); + } + + @override + void dispose() { + _limitController.dispose(); + super.dispose(); + } + + Future _showAddOrEditLimitDialog() async { + _limitController.text = _currentLimit?.toStringAsFixed(0) ?? ''; + final newLimit = await showDialog( + context: context, + builder: (context) { + final localizations = AppLocalizations.of(context); + 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(context).pop(), + child: Text(localizations.cancel), + ), + ElevatedButton( + onPressed: () async { + final value = double.tryParse(_limitController.text); + if (value != null && value > 0) { + setState(() { + service.editLimit(value); + }); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const NavigationScaffold(), + ), + ); + } + }, + child: Text(localizations.save), + ), + ], + ); + }, + ); + if (newLimit != null) { + setState(() { + _currentLimit = newLimit; + }); + } + } + + void _removeLimit() { + setState(() { + _currentLimit = null; + }); + } + + @override + Widget build(BuildContext context) { + _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; + + 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 6798f75..82f5e79 100644 --- a/lib/features/profile/profile_screen.dart +++ b/lib/features/profile/profile_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:kmobile/data/repositories/auth_repository.dart'; import 'package:kmobile/features/profile/change_password/change_password_screen.dart'; +import 'package:kmobile/features/profile/daily_transaction_limit.dart'; import 'package:kmobile/features/profile/logout_dialog.dart'; import 'package:kmobile/security/secure_storage.dart'; import 'package:local_auth/local_auth.dart'; @@ -155,6 +156,17 @@ class _ProfileScreenState extends State { ); }, ), + ListTile( + leading: const Icon(Icons.currency_rupee), + title: Text(AppLocalizations.of(context).dailylimit), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const DailyLimitScreen()), + ); + }, + ), SwitchListTile( title: Text(AppLocalizations.of(context).enableFingerprintLogin), value: _isBiometricEnabled, diff --git a/lib/features/service/screens/daily_transaction_limit.dart b/lib/features/service/screens/daily_transaction_limit.dart deleted file mode 100644 index fd242b4..0000000 --- a/lib/features/service/screens/daily_transaction_limit.dart +++ /dev/null @@ -1,161 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:kmobile/l10n/app_localizations.dart'; -import 'package:intl/intl.dart'; - -class DailyLimitScreen extends StatefulWidget { - const DailyLimitScreen({super.key}); - @override - State createState() => _DailyLimitScreenState(); -} - -class _DailyLimitScreenState extends State { - double? _currentLimit; - final _limitController = TextEditingController(); - - @override - void initState() { - super.initState(); - // Now just taking null, but for real time limit will be fetched using API call - _currentLimit = null; - } - - @override - void dispose() { - _limitController.dispose(); - super.dispose(); - } - - Future _showAddOrEditLimitDialog() async { - _limitController.text = _currentLimit?.toStringAsFixed(0) ?? ''; - final newLimit = await showDialog( - context: context, - builder: (context) { - final localizations = AppLocalizations.of(context); - 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(context).pop(), - child: Text(localizations.cancel), - ), - ElevatedButton( - onPressed: () { - final value = double.tryParse(_limitController.text); - if (value != null && value > 0) { - Navigator.of(context).pop(value); - } - }, - child: Text(localizations.save), - ), - ], - ); - }, - ); - if (newLimit != null) { - setState(() { - _currentLimit = newLimit; - }); - } - } - - void _removeLimit() { - setState(() { - _currentLimit = null; - }); - } - - @override - Widget build(BuildContext context) { - final localizations = AppLocalizations.of(context); - final theme = Theme.of(context); - final formatCurrency = NumberFormat.currency(locale: 'en_IN', symbol: '₹'); - 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, - ), - ), - 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/service/screens/service_screen.dart b/lib/features/service/screens/service_screen.dart index 218d4a5..a036384 100644 --- a/lib/features/service/screens/service_screen.dart +++ b/lib/features/service/screens/service_screen.dart @@ -1,5 +1,5 @@ import 'package:kmobile/features/service/screens/branch_locator_screen.dart'; -import 'package:kmobile/features/service/screens/daily_transaction_limit.dart'; + import '../../../l10n/app_localizations.dart'; import 'package:flutter/material.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; @@ -40,18 +40,6 @@ class _ServiceScreen extends State { disabled: true, ), const Divider(height: 1), - ServiceManagementTile( - icon: Symbols.currency_rupee, - label: AppLocalizations.of(context).dailylimit, - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const DailyLimitScreen()), - ); - }, - disabled: true, - ), - const Divider(height: 1), ServiceManagementTile( icon: Symbols.captive_portal, label: AppLocalizations.of(context).quickLinks,