5 Commits

Author SHA1 Message Date
5823eaede8 After Login Button 2025-12-08 17:45:12 +05:30
1ae3e7c0a6 Limit Added to SMS enabled device 2025-11-04 15:29:56 +05:30
530e5c0493 Header Added 2025-10-28 18:04:14 +05:30
e1c1a58086 SMS Screen Modifiaction 2025-10-28 17:59:05 +05:30
dd3e94a69e SMS succesfully sent 2025-10-27 17:36:28 +05:30
22 changed files with 1280 additions and 716 deletions

View File

@@ -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<String, dynamic> json) {
return Limit(
dailyLimit: json['dailyLimit']!,
usedLimit: json['usedLimit']!,
);
}
}
class LimitService {
final Dio _dio;
LimitService(this._dio);
Future<Limit> 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()}');
}
}
}

View File

@@ -1,7 +1,7 @@
// // ignore_for_file: avoid_print
// import 'dart:io';
// import 'package:flutter/material.dart';
// import 'send_sms.dart';
// import 'package:send_message/send_message.dart' show sendSMS;
// import 'package:simcards/sim_card.dart';
// import 'package:simcards/simcards.dart';
@@ -49,7 +49,7 @@
// String result = await sendSMS(
// message: smsMessage,
// recipients: [destinationNumber],
// sendDirect: true,
// sendDirect: false,
// );
// print("SMS send result: $result. Sent via ${selectedSim.displayName} (Note: OS default SIM isused).");
@@ -62,68 +62,186 @@
// }
// }
// ignore_for_file: avoid_print
// import 'dart:io';
// import 'package:flutter/material.dart';
// import 'package:permission_handler/permission_handler.dart'; // Import permission_handler
// import 'package:send_message/send_message.dart' show sendSMS;
// import 'package:simcards/sim_card.dart';
// import 'package:simcards/simcards.dart';
// class SmsService {
// final Simcards _simcards = Simcards();
// Future<bool> sendVerificationSms({
// required BuildContext context,
// required String destinationNumber,
// required String message,
// }) async {
// try {
// // --- NEW PERMISSION LOGIC ---
// // 1. Request both Phone and SMS permissions
// Map<Permission, PermissionStatus> statuses = await [
// Permission.phone,
// Permission.sms,
// ].request();
// // 2. Check if both permissions were granted
// if (statuses[Permission.phone]!.isGranted && statuses[Permission.sms]!.isGranted) {
// print("Phone and SMS permissions are granted.");
// } else {
// print("Permission was denied. Phone status: ${statuses[Permission.phone]}, SMS status: ${statuses[Permission.sms]}");
// // Optionally, you can open app settings to let the user grant it manually
// // openAppSettings();
// return false;
// }
// // --- END OF NEW PERMISSION LOGIC ---
// // Check for SIM card (this part remains the same)
// List<SimCard> simCardList = await _simcards.getSimCards();
// if (simCardList.isEmpty) {
// print("No SIM card detected.");
// return false;
// }
// // Try sending the SMS and return the result
// return await _sendSms(destinationNumber, message, simCardList.first);
// } catch (e) {
// print("An error occurred in the SMS process: $e");
// return false;
// }
// }
// Future<bool> _sendSms(
// String destinationNumber, String message, SimCard selectedSim) async {
// if (Platform.isAndroid) {
// try {
// String smsMessage = message;
// String result = await sendSMS(
// message: smsMessage,
// recipients: [destinationNumber],
// sendDirect: true, // Still attempting direct send as requested
// );
// print("Background SMS send attempt result: $result");
// if (result.toLowerCase().contains('sent')) {
// print("Success: SMS appears to have been sent.");
// return true;
// } else {
// print("Failure: SMS was not sent. Result: $result");
// return false;
// }
// } catch (e) {
// print("Error attempting to send SMS directly: $e");
// return false;
// }
// } else {
// print("SMS sending is only supported on Android.");
// return false;
// }
// }
// }
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_sms/flutter_sms.dart'; // <-- 1. IMPORT the new package
import 'package:permission_handler/permission_handler.dart';
import 'package:send_message/send_message.dart' show sendSMS;
import 'package:simcards/sim_card.dart';
import 'package:simcards/simcards.dart';
import 'package:uuid/uuid.dart';
// This enum provides detailed status back to the UI layer.
enum PermissionStatusResult { granted, denied, permanentlyDenied, restricted }
class SmsService {
final Simcards _simcards = Simcards();
Future<void> sendVerificationSms({
/// Handles the requesting of SMS and Phone permissions.
/// Returns a detailed status: granted, denied, or permanentlyDenied.
Future<PermissionStatusResult> handleSmsPermission() async {
var smsStatus = await Permission.sms.status;
var phoneStatus = await Permission.phone.status;
// Check initial status
if (smsStatus.isGranted && phoneStatus.isGranted) {
return PermissionStatusResult.granted;
}
if (smsStatus.isPermanentlyDenied || phoneStatus.isPermanentlyDenied) {
return PermissionStatusResult.permanentlyDenied;
}
if (smsStatus.isRestricted || phoneStatus.isRestricted) {
return PermissionStatusResult.restricted;
}
// Request permissions if not granted
print("Requesting SMS and Phone permissions...");
await [Permission.phone, Permission.sms].request();
// Re-check status after request
smsStatus = await Permission.sms.status;
phoneStatus = await Permission.phone.status;
if (smsStatus.isGranted && phoneStatus.isGranted) {
return PermissionStatusResult.granted;
}
if (smsStatus.isPermanentlyDenied || phoneStatus.isPermanentlyDenied) {
return PermissionStatusResult.permanentlyDenied;
}
if (smsStatus.isRestricted || phoneStatus.isRestricted) {
return PermissionStatusResult.restricted;
}
// If none of the above, it's denied
return PermissionStatusResult.denied;
}
/// Tries to send a single verification SMS.
/// This should only be called AFTER permissions have been granted.
Future<bool> sendVerificationSms({
required BuildContext context,
required String destinationNumber,
required String message,
}) async {
try {
await _simcards.requestPermission();
bool permissionGranted = await _simcards.hasPermission();
if (!permissionGranted) {
print("Permission denied.");
return;
}
List<SimCard> simCardList = await _simcards.getSimCards();
if (simCardList.isEmpty) {
print("No SIM detected.");
return;
print("No SIM card detected.");
return false;
}
await _sendSms(destinationNumber, message, simCardList.first);
return await _sendSms(destinationNumber, message, simCardList.first);
} catch (e) {
print("Error in SMS process: $e");
print("An error occurred in the SMS process: $e");
return false;
}
}
Future<void> _sendSms(
/// Private function to perform the SMS sending action.
Future<bool> _sendSms(
String destinationNumber, String message, SimCard selectedSim) async {
if (Platform.isAndroid) {
try {
var uuid = const Uuid();
String uniqueId = uuid.v4();
String smsMessage = uniqueId;
// v-- 2. UPDATE the function call below --v
String smsMessage = message;
String result = await sendSMS(
message: smsMessage,
recipients: [destinationNumber],
sendDirect: true,
);
// ^-- The 'sendDirect' parameter is not available in this package. --^
// It will open the user's default messaging app with the fields pre-filled.
print("Background SMS send attempt result: $result");
print(
"SMS send result: $result. Sent via ${selectedSim.displayName} (Note: OS default SIM isused)."
);
if (result.toLowerCase().contains('sent')) {
print("Success: SMS appears to have been sent.");
return true;
} else {
print("Failure: SMS was not sent. Result: $result");
return false;
}
} catch (e) {
print("Error sending SMS: $e");
print("Error attempting to send SMS directly: $e");
return false;
}
} else {
print("SMS sending is only supported on Android.");
return false;
}
}
}

View File

@@ -4,6 +4,8 @@ import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kmobile/features/auth/controllers/theme_mode_cubit.dart';
import 'package:kmobile/features/auth/controllers/theme_mode_state.dart';
import 'package:kmobile/features/auth/screens/login_screen.dart';
//import 'package:kmobile/features/auth/screens/sms_verification_screen.dart';
import 'package:kmobile/security/secure_storage.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import './l10n/app_localizations.dart';
@@ -13,8 +15,6 @@ import 'config/routes.dart';
import 'di/injection.dart';
import 'features/auth/controllers/auth_cubit.dart';
import 'features/card/screens/card_management_screen.dart';
import 'features/auth/screens/splash_screen.dart';
import 'features/auth/screens/login_screen.dart';
import 'features/service/screens/service_screen.dart';
import 'features/dashboard/screens/dashboard_screen.dart';
import 'features/auth/screens/mpin_screen.dart';
@@ -37,7 +37,6 @@ class KMobile extends StatefulWidget {
class _KMobileState extends State<KMobile> with WidgetsBindingObserver {
Timer? _backgroundTimer;
bool showSplash = true;
Locale? _locale;
@override
@@ -45,11 +44,6 @@ class _KMobileState extends State<KMobile> with WidgetsBindingObserver {
super.initState();
WidgetsBinding.instance.addObserver(this);
loadPreferences();
Future.delayed(const Duration(seconds: 3), () {
setState(() {
showSplash = false;
});
});
}
@override
@@ -132,8 +126,7 @@ class _KMobileState extends State<KMobile> with WidgetsBindingObserver {
darkTheme: themeState.getDarkThemeData(),
themeMode: context.watch<ThemeModeCubit>().state.mode,
onGenerateRoute: AppRoutes.generateRoute,
initialRoute: AppRoutes.splash,
home: showSplash ? const SplashScreen() : const AuthGate(),
home: const AuthGate(),
);
},
);
@@ -205,7 +198,11 @@ class _AuthGateState extends State<AuthGate> {
@override
Widget build(BuildContext context) {
if (_checking) {
return const SplashScreen();
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
if (_isLoggedIn) {
if (_hasMPin) {
@@ -214,7 +211,11 @@ class _AuthGateState extends State<AuthGate> {
future: _tryBiometric(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const SplashScreen();
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
if (snapshot.data == true) {
return const NavigationScaffold();
@@ -422,7 +423,11 @@ class BiometricPromptScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
Future.microtask(() => _showDialog(context));
return const SplashScreen();
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
Future<void> _showDialog(BuildContext context) async {

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:kmobile/features/auth/screens/mpin_screen.dart';
import 'package:kmobile/features/auth/screens/splash_screen.dart';
import '../app.dart';
import '../features/auth/screens/login_screen.dart';
// import '../features/auth/screens/forgot_password_screen.dart';
@@ -30,8 +29,6 @@ class AppRoutes {
// Route generator
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case splash:
return MaterialPageRoute(builder: (_) => const SplashScreen());
case login:
return MaterialPageRoute(builder: (_) => const LoginScreen());

View File

@@ -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<void> setupDependencies() async {
getIt.registerSingleton<PaymentService>(PaymentService(getIt<Dio>()));
getIt.registerSingleton<BeneficiaryService>(BeneficiaryService(getIt<Dio>()));
getIt.registerSingleton<LimitService>(LimitService(getIt<Dio>()));
getIt.registerSingleton<NeftService>(NeftService(getIt<Dio>()));
getIt.registerSingleton<RtgsService>(RtgsService(getIt<Dio>()));
getIt.registerSingleton<ImpsService>(ImpsService(getIt<Dio>()));
@@ -67,14 +69,15 @@ 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',
'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',
},
),
);

View File

@@ -12,6 +12,10 @@ class AuthCubit extends Cubit<AuthState> {
checkAuthStatus();
}
void reset() {
emit(AuthInitial());
}
Future<void> checkAuthStatus() async {
emit(AuthLoading());
try {
@@ -27,6 +31,10 @@ class AuthCubit extends Cubit<AuthState> {
}
}
void startVerification() {
emit(AuthVerificationInProgress());
}
Future<void> refreshUserData() async {
try {
// emit(AuthLoading());

View File

@@ -10,6 +10,8 @@ class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthVerificationInProgress extends AuthState {}
class Authenticated extends AuthState {
final List<User> users;

View File

@@ -1,14 +1,8 @@
import '../../../l10n/app_localizations.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/features/auth/screens/mpin_screen.dart';
import 'package:kmobile/features/auth/screens/set_password_screen.dart';
import 'package:kmobile/security/secure_storage.dart';
import '../../../app.dart';
import '../controllers/auth_cubit.dart';
import '../controllers/auth_state.dart';
import 'package:kmobile/features/auth/controllers/auth_cubit.dart';
import 'package:kmobile/features/auth/screens/verification_screen.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@@ -23,7 +17,12 @@ class LoginScreenState extends State<LoginScreen>
final _customerNumberController = TextEditingController();
final _passwordController = TextEditingController();
bool _obscurePassword = true;
//bool _showWelcome = true;
@override
void initState() {
super.initState();
context.read<AuthCubit>().reset();
}
@override
void dispose() {
@@ -34,9 +33,13 @@ class LoginScreenState extends State<LoginScreen>
void _submitForm() {
if (_formKey.currentState!.validate()) {
context.read<AuthCubit>().login(
_customerNumberController.text.trim(),
_passwordController.text,
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => VerificationScreen(
customerNo: _customerNumberController.text.trim(),
password: _passwordController.text,
),
),
);
}
}
@@ -45,49 +48,7 @@ class LoginScreenState extends State<LoginScreen>
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(title: const Text('Login')),
body: BlocConsumer<AuthCubit, AuthState>(
listener: (context, state) async {
if (state is Authenticated) {
final storage = getIt<SecureStorage>();
final mpin = await storage.read('mpin');
if (!context.mounted) return;
if (mpin == null) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => MPinScreen(
mode: MPinMode.set,
onCompleted: (_) {
Navigator.of(
context,
rootNavigator: true,
).pushReplacement(
MaterialPageRoute(
builder: (_) => const NavigationScaffold(),
),
);
},
),
),
);
} else {
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) {
return Padding(
body: Padding(
padding: const EdgeInsets.all(24.0),
child: Form(
key: _formKey,
@@ -193,7 +154,7 @@ class LoginScreenState extends State<LoginScreen>
SizedBox(
width: 250,
child: ElevatedButton(
onPressed: state is AuthLoading ? null : _submitForm,
onPressed: _submitForm,
style: ElevatedButton.styleFrom(
shape: const StadiumBorder(),
padding: const EdgeInsets.symmetric(vertical: 16),
@@ -205,9 +166,7 @@ class LoginScreenState extends State<LoginScreen>
width: 1),
elevation: 0,
),
child: state is AuthLoading
? const CircularProgressIndicator()
: Text(
child: Text(
AppLocalizations.of(context).login,
style: TextStyle(
color: Theme.of(context)
@@ -218,44 +177,12 @@ class LoginScreenState extends State<LoginScreen>
),
const SizedBox(height: 15),
// Padding(
// padding: const EdgeInsets.symmetric(vertical: 16),
// child: Row(
// children: [
// const Expanded(child: Divider()),
// Padding(
// padding: const EdgeInsets.symmetric(horizontal: 8),
// child: Text(AppLocalizations.of(context).or),
// ),
// //const Expanded(child: Divider()),
// ],
// ),
// ),
const SizedBox(height: 25),
// Register Button
// SizedBox(
// width: 250,
// child: ElevatedButton(
// //disable until registration is implemented
// onPressed: null,
// style: OutlinedButton.styleFrom(
// shape: const StadiumBorder(),
// padding: const EdgeInsets.symmetric(vertical: 16),
// backgroundColor: Theme.of(context).colorScheme.primary,
// foregroundColor: Theme.of(context).colorScheme.onPrimary,
// ),
// child: Text(AppLocalizations.of(context).register,
// style: TextStyle(color: Theme.of(context).colorScheme.onPrimary),),
// ),
// ),
],
),
),
);
},
),
);
}
}

View File

@@ -0,0 +1,131 @@
// lib/features/auth/screens/sms_verification_helper.dart
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/send_sms_service.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:uuid/uuid.dart';
class SmsVerificationHelper {
final SmsService _smsService = SmsService();
Future<void> _showRestrictedSmsDialog(BuildContext context) async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("SMS Permission Restricted"),
content: const SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("It seems your device is restricting this app from sending SMS messages, which is required for verification. Please follow these steps to enable it:\n"),
Text("1. Open your device Settings.", style: TextStyle(fontWeight: FontWeight.bold)),
Text("2. Go to 'Apps' or 'Apps & notifications'."),
Text("3. Find and tap on this app ('KMobile')."),
Text("4. Tap on the three dots (⋮) in the top right corner."),
Text("5. Select 'Allow restricted settings' and confirm. This is crucial to allow SMS permission."),
Text("6. Now you have two options to allow SMS permission:"),
Text(" a. Tap on 'Permissions', then find 'SMS' is set to 'Allow'."),
Text(" b. Alternatively, you can return to the KMobile app, and the SMS permission pop-up should appear again, allowing you to grant it directly."),
Text("\nSome devices have an additional setting for 'Premium SMS'. If the above doesn't work, look for a 'Premium SMS access' setting (you can search for it in your Settings app) and set it to 'Always Allow' for this app.\n"),
Text("After you've enabled the permission, please come back to the app."),
],
),
),
actions: [
TextButton(
child: const Text("I've Enabled It"),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: const Text("Open Settings"),
onPressed: () {
openAppSettings();
Navigator.of(context).pop();
},
),
],
),
);
}
void _showSnackBar(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
duration: const Duration(seconds: 3),
),
);
}
Future<void> initiateSmsSequence({
required BuildContext context,
}) async {
bool hasPermission = false;
// --- PERMISSION LOOP ---
while (!hasPermission) {
final status = await _smsService.handleSmsPermission();
switch (status) {
case PermissionStatusResult.granted:
_showSnackBar(context, "Permissions Granted! Proceeding...");
hasPermission = true; // This will break the loop
break;
case PermissionStatusResult.denied:
_showSnackBar(context, "SMS and Phone permissions are required. Please try again.");
await Future.delayed(const Duration(seconds: 3));
break;
case PermissionStatusResult.permanentlyDenied:
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("Permission Required"),
content: const Text("SMS and Phone permissions are required for device verification. Please enable them in your app settings to continue."),
actions: [
TextButton(
child: const Text("Cancel"),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: const Text("Open Settings"),
onPressed: () {
openAppSettings(); // Opens the phone's settings screen for this app
Navigator.of(context).pop();
},
),
],
),
);
// Wait for user to return from settings
await Future.delayed(const Duration(seconds: 5));
break;
case PermissionStatusResult.restricted:
await _showRestrictedSmsDialog(context);
// Wait for user to return from settings
await Future.delayed(const Duration(seconds: 10));
break;
}
}
// --- SMS SENDING LOOP ---
bool isSmsSent = false;
while (!isSmsSent) {
var uuid = const Uuid();
String uniqueId = uuid.v4();
String smsMessage = uniqueId;
_showSnackBar(context, "Attempting to send verification SMS...");
isSmsSent = await _smsService.sendVerificationSms(
context: context,
destinationNumber: '9580079717', // Replace with your number
message: smsMessage,
);
if (isSmsSent) {
_showSnackBar(context, "SMS sent successfully! Proceeding to login.");
break;
} else {
_showSnackBar(context, "SMS failed to send. Retrying in 5 seconds...");
await Future.delayed(const Duration(seconds: 5));
}
}
}
}

View File

@@ -1,103 +0,0 @@
import 'package:package_info_plus/package_info_plus.dart';
import '../../../l10n/app_localizations.dart';
import 'package:kmobile/api/services/send_sms_service.dart';
import 'package:flutter/material.dart';
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
String _version = '';
final SmsService _smsService = SmsService();
@override
void initState() {
super.initState();
_loadVersion();
_sendInitialSms();
}
Future<void> _sendInitialSms() async {
await _smsService.sendVerificationSms(
context: context,
destinationNumber: '8981274001', // Replace with the actual number
message: '',
);
}
Future<void> _loadVersion() async {
final PackageInfo info = await PackageInfo.fromPlatform();
if (mounted) {
// Check if the widget is still in the tree
setState(() {
_version = 'Version ${info.version} (${info.buildNumber})';
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: <Widget>[
Positioned.fill(
child: Image.asset(
'assets/images/kconnect2.webp',
fit: BoxFit.cover,
),
),
Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
AppLocalizations.of(context).kccbMobile,
style: const TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Color(0xFFFFFFFF),
),
),
const SizedBox(height: 12),
Text(
AppLocalizations.of(context).kccBankFull,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 18,
color: Color(0xFFFFFFFF),
letterSpacing: 1.2,
),
),
],
),
),
const Positioned(
bottom: 40,
left: 0,
right: 0,
child: Center(
child: CircularProgressIndicator(color: Color(0xFFFFFFFF)),
),
),
Positioned(
bottom: 90,
left: 0,
right: 0,
child: Text(
_version,
textAlign: TextAlign.center,
style: const TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 14,
),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,146 @@
// lib/features/auth/screens/verification_screen.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kmobile/features/auth/controllers/auth_cubit.dart';
import 'package:kmobile/features/auth/controllers/auth_state.dart';
import 'package:kmobile/features/auth/screens/mpin_screen.dart';
import 'package:kmobile/features/auth/screens/sms_verification_helper.dart';
import '../../../app.dart';
class VerificationScreen extends StatefulWidget {
final String customerNo;
final String password;
const VerificationScreen({
super.key,
required this.customerNo,
required this.password,
});
@override
State<VerificationScreen> createState() => _VerificationScreenState();
}
class _VerificationScreenState extends State<VerificationScreen> {
final SmsVerificationHelper _smsVerificationHelper = SmsVerificationHelper();
late Timer _timer;
int _start = 120;
String _message = "Attempting verification...";
String? _error;
@override
void initState() {
super.initState();
context.read<AuthCubit>().startVerification();
startTimer();
_verifySmsAndLogin();
}
void startTimer() {
const oneSec = Duration(seconds: 1);
_timer = Timer.periodic(
oneSec,
(Timer timer) {
if (_start == 0) {
timer.cancel();
if (mounted) {
setState(() {
_error = "Verification timed out.";
});
}
} else {
setState(() {
_start--;
});
}
},
);
}
Future<void> _verifySmsAndLogin() async {
await _smsVerificationHelper.initiateSmsSequence(context: context);
// After SMS sequence completes, proceed with login
_timer.cancel(); // Stop the timer
if (mounted) {
context.read<AuthCubit>().login(widget.customerNo, widget.password);
}
}
@override
void dispose() {
_timer.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocListener<AuthCubit, AuthState>(
listenWhen: (previous, current) {
return current is! AuthVerificationInProgress && current is! AuthInitial;
},
listener: (context, state) {
if (state is Authenticated) {
_timer.cancel();
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => MPinScreen(
mode: MPinMode.set,
onCompleted: (_) {
Navigator.of(
context,
rootNavigator: true,
).pushReplacement(
MaterialPageRoute(
builder: (_) => const NavigationScaffold(),
),
);
},
),
),
);
} else if (state is AuthError) {
_timer.cancel();
setState(() {
_error = state.message;
});
}
},
child: Center(
child: _error != null
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error, color: Colors.red, size: 80),
const SizedBox(height: 16),
Text(
_error!,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.red, fontSize: 18),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text("Back to Login"),
)
],
)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const CircularProgressIndicator(),
const SizedBox(height: 16),
Text(_message),
const SizedBox(height: 16),
Text("Time remaining: $_start seconds"),
],
),
),
),
);
}
}

View File

@@ -2,6 +2,8 @@ import 'dart:async';
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:kmobile/api/services/limit_service.dart';
import 'package:kmobile/api/services/neft_service.dart';
import 'package:kmobile/api/services/rtgs_service.dart';
import 'package:kmobile/api/services/imps_service.dart';
@@ -40,13 +42,67 @@ class FundTransferAmountScreen extends StatefulWidget {
}
class _FundTransferAmountScreenState extends State<FundTransferAmountScreen> {
final _limitService = getIt<LimitService>();
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<FormState>();
TransactionMode _selectedMode = TransactionMode.neft;
@override
void initState() {
super.initState();
_loadLimit(); // Call the new method
_amountController.addListener(_checkAmountLimit);
}
Future<void> _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,
),
);
}
if (_isAmountOverLimit != isOverLimit) {
setState(() {
_isAmountOverLimit = isOverLimit;
});
}
}
@override
void dispose() {
_amountController.removeListener(_checkAmountLimit);
_amountController.dispose();
_remarksController.dispose();
super.dispose();
@@ -429,6 +485,14 @@ class _FundTransferAmountScreenState extends State<FundTransferAmountScreen> {
}
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 Spacer(),
@@ -436,7 +500,7 @@ class _FundTransferAmountScreenState extends State<FundTransferAmountScreen> {
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _onProceed,
onPressed: _isAmountOverLimit ? null : _onProceed,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),

View File

@@ -0,0 +1,228 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:kmobile/api/services/limit_service.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<DailyLimitScreen> createState() => _DailyLimitScreenState();
}
class _DailyLimitScreenState extends State<DailyLimitScreen> {
double? _currentLimit;
double? _spentAmount = 0.0;
final _limitController = TextEditingController();
var service = getIt<LimitService>();
Limit? limit;
bool _isLoading = true;
@override
void initState() {
super.initState();
_loadlimits();
}
Future<void> _loadlimits() async {
setState(() {
_isLoading = true;
});
final limit_data = await service.getLimit();
setState(() {
limit = limit_data;
_isLoading = false;
});
}
@override
void dispose() {
_limitController.dispose();
super.dispose();
}
Future<void> _showAddOrEditLimitDialog() async {
_limitController.text = _currentLimit?.toStringAsFixed(0) ?? '';
final newLimit = await showDialog<double>(
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),
),
],
);
},
);
if (newLimit != null) {
_loadlimits();
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Limit Updated"),
behavior: SnackBarBehavior.floating,
),
);
}
}
void _removeLimit() {
setState(() {
_currentLimit = null;
});
}
@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;
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,
// ),
// ),
],
),
],
),
),
),
);
}
}

View File

@@ -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';
@@ -37,7 +38,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
final storage = getIt<SecureStorage>();
final isEnabled = await storage.read('biometric_enabled');
setState(() {
_isBiometricEnabled = isEnabled == 'true';
_isBiometricEnabled = isEnabled == true;
});
}
@@ -155,6 +156,17 @@ class _ProfileScreenState extends State<ProfileScreen> {
);
},
),
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,

View File

@@ -2,7 +2,9 @@ import 'dart:async';
import 'dart:developer';
import 'package:dio/dio.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:kmobile/api/services/imps_service.dart';
import 'package:kmobile/api/services/limit_service.dart';
import 'package:kmobile/api/services/neft_service.dart';
import 'package:kmobile/api/services/rtgs_service.dart';
import 'package:kmobile/data/models/imps_transaction.dart';
@@ -28,7 +30,10 @@ class QuickPayOutsideBankScreen extends StatefulWidget {
class _QuickPayOutsideBankScreen extends State<QuickPayOutsideBankScreen> {
final _formKey = GlobalKey<FormState>();
final _limitService = getIt<LimitService>();
Limit? _limit;
bool _isLoadingLimit = true;
final _formatCurrency = NumberFormat.currency(locale: 'en_IN', symbol: '');
// Controllers
final accountNumberController = TextEditingController();
final confirmAccountNumberController = TextEditingController();
@@ -41,6 +46,7 @@ class _QuickPayOutsideBankScreen extends State<QuickPayOutsideBankScreen> {
final remarksController = TextEditingController();
final _ifscFocusNode = FocusNode();
final service = getIt<BeneficiaryService>();
bool _isAmountOverLimit = false;
late String accountType;
bool _isValidating = false;
@@ -50,6 +56,7 @@ class _QuickPayOutsideBankScreen extends State<QuickPayOutsideBankScreen> {
@override
void initState() {
super.initState();
_loadLimit();
_ifscFocusNode.addListener(() {
if (!_ifscFocusNode.hasFocus && ifscController.text.trim().length == 11) {
_validateIFSC();
@@ -60,6 +67,49 @@ class _QuickPayOutsideBankScreen extends State<QuickPayOutsideBankScreen> {
accountType = 'Savings';
});
});
amountController.addListener(_checkAmountLimit);
}
Future<void> _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,
),
);
}
if (_isAmountOverLimit != isOverLimit) {
setState(() {
_isAmountOverLimit = isOverLimit;
});
}
}
void _validateIFSC() async {
@@ -718,6 +768,9 @@ class _QuickPayOutsideBankScreen extends State<QuickPayOutsideBankScreen> {
),
),
const SizedBox(height: 25),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
@@ -782,6 +835,22 @@ class _QuickPayOutsideBankScreen extends State<QuickPayOutsideBankScreen> {
),
),
],
),
],
),
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,
),
),
const SizedBox(height: 30),
Row(
@@ -799,13 +868,20 @@ class _QuickPayOutsideBankScreen extends State<QuickPayOutsideBankScreen> {
alignment: Alignment.center,
child: SwipeButton.expand(
thumb: Icon(Icons.arrow_forward,
color: Theme.of(context).dialogBackgroundColor),
activeThumbColor: Theme.of(context).colorScheme.primary,
activeTrackColor:
Theme.of(context).colorScheme.secondary.withAlpha(100),
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: _onProceedToPay,
onSwipe: () {
if (_isAmountOverLimit) {
return; // Do nothing if amount is over the limit
}
_onProceedToPay();
},
child: Text(
AppLocalizations.of(context).swipeToPay,
style: const TextStyle(

View File

@@ -1,7 +1,9 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_swipe_button/flutter_swipe_button.dart';
import 'package:intl/intl.dart';
import 'package:kmobile/api/services/beneficiary_service.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/di/injection.dart';
@@ -19,14 +21,17 @@ class QuickPayWithinBankScreen extends StatefulWidget {
class _QuickPayWithinBankScreen extends State<QuickPayWithinBankScreen> {
final _formKey = GlobalKey<FormState>();
final _limitService = getIt<LimitService>();
Limit? _limit;
bool _isLoadingLimit = true;
final _formatCurrency = NumberFormat.currency(locale: 'en_IN', symbol: '');
final TextEditingController accountNumberController = TextEditingController();
final TextEditingController confirmAccountNumberController =
TextEditingController();
final TextEditingController amountController = TextEditingController();
final TextEditingController remarksController = TextEditingController();
String? _selectedAccountType;
bool _isAmountOverLimit = false;
String? _beneficiaryName;
bool _isValidating = false;
bool _isBeneficiaryValidated = false;
@@ -35,8 +40,52 @@ class _QuickPayWithinBankScreen extends State<QuickPayWithinBankScreen> {
@override
void initState() {
super.initState();
_loadLimit();
accountNumberController.addListener(_resetBeneficiaryValidation);
confirmAccountNumberController.addListener(_resetBeneficiaryValidation);
amountController.addListener(_checkAmountLimit);
}
Future<void> _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;
});
}
}
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() {
@@ -53,6 +102,7 @@ class _QuickPayWithinBankScreen extends State<QuickPayWithinBankScreen> {
@override
void dispose() {
amountController.removeListener(_checkAmountLimit);
accountNumberController.removeListener(_resetBeneficiaryValidation);
confirmAccountNumberController.removeListener(_resetBeneficiaryValidation);
accountNumberController.dispose();
@@ -102,6 +152,7 @@ class _QuickPayWithinBankScreen extends State<QuickPayWithinBankScreen> {
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 10),
@@ -297,6 +348,7 @@ class _QuickPayWithinBankScreen extends State<QuickPayWithinBankScreen> {
),
),
const SizedBox(height: 25),
TextFormField(
decoration: InputDecoration(
labelText: AppLocalizations.of(context).amount,
@@ -326,15 +378,26 @@ class _QuickPayWithinBankScreen extends State<QuickPayWithinBankScreen> {
}
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: Theme.of(context).dialogBackgroundColor),
activeThumbColor: Theme.of(context).colorScheme.primary,
activeTrackColor: Theme.of(
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),
@@ -344,6 +407,9 @@ class _QuickPayWithinBankScreen extends State<QuickPayWithinBankScreen> {
style: const TextStyle(fontSize: 16),
),
onSwipe: () {
if (_isAmountOverLimit) {
return; // Do nothing if amount is over limit
}
if (_formKey.currentState!.validate()) {
if (!_isBeneficiaryValidated) {
setState(() {
@@ -389,6 +455,7 @@ class _QuickPayWithinBankScreen extends State<QuickPayWithinBankScreen> {
),
),
),
),
);
}

View File

@@ -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<DailyLimitScreen> createState() => _DailyLimitScreenState();
}
class _DailyLimitScreenState extends State<DailyLimitScreen> {
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<void> _showAddOrEditLimitDialog() async {
_limitController.text = _currentLimit?.toStringAsFixed(0) ?? '';
final newLimit = await showDialog<double>(
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,
),
),
],
),
],
),
),
),
);
}
}

View File

@@ -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 'package:kmobile/features/profile/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<ServiceScreen> {
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,

View File

@@ -17,14 +17,14 @@ void main() async {
]);
// Check for device compromise
// final compromisedMessage = await SecurityService.deviceCompromisedMessage;
// if (compromisedMessage != null) {
// Logger.error("Device compromised: $compromisedMessage");
// runApp(MaterialApp(
// home: SecurityErrorScreen(message: compromisedMessage),
// ));
// return;
// }
final compromisedMessage = await SecurityService.deviceCompromisedMessage;
if (compromisedMessage != null) {
Logger.error("Device compromised: $compromisedMessage");
runApp(MaterialApp(
home: SecurityErrorScreen(message: compromisedMessage),
));
return;
}
Logger.info("Setting up dependencies...");
await setupDependencies();
Logger.info("Dependencies set up.");

View File

@@ -22,7 +22,7 @@ Widget getBankLogo(String? bankName, BuildContext context) {
height: 40,
);
}
if (bankName != null && bankName.toLowerCase().contains('icici bank ltd')) {
if (bankName != null && bankName.toLowerCase().contains('icici')) {
return Image.asset(
'assets/images/icici_logo.png',
width: 40,

View File

@@ -69,10 +69,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.3.0"
version: "1.4.0"
checked_yaml:
dependency: transitive
description:
@@ -93,18 +93,18 @@ packages:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.1"
version: "1.1.2"
collection:
dependency: transitive
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.18.0"
version: "1.19.1"
confetti:
dependency: "direct main"
description:
@@ -181,10 +181,10 @@ packages:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
version: "1.3.3"
ffi:
dependency: transitive
description:
@@ -307,14 +307,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.2"
flutter_sms:
dependency: "direct main"
description:
name: flutter_sms
sha256: "2fe5f584f02596343557eeca56348f9b82413fefe83a423fab880cdbdf54d8d8"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
flutter_svg:
dependency: "direct main"
description:
@@ -401,10 +393,10 @@ packages:
dependency: "direct main"
description:
name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev"
source: hosted
version: "0.19.0"
version: "0.20.2"
jailbreak_root_detection:
dependency: "direct main"
description:
@@ -433,26 +425,26 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "10.0.5"
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.5"
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "3.0.2"
lints:
dependency: transitive
description:
@@ -513,10 +505,10 @@ packages:
dependency: transitive
description:
name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.16+1"
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
@@ -537,10 +529,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev"
source: hosted
version: "1.15.0"
version: "1.16.0"
mime:
dependency: transitive
description:
@@ -577,10 +569,10 @@ packages:
dependency: transitive
description:
name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.0"
version: "1.9.1"
path_parsing:
dependency: transitive
description:
@@ -649,18 +641,18 @@ packages:
dependency: "direct main"
description:
name: permission_handler
sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849"
url: "https://pub.dev"
source: hosted
version: "12.0.1"
version: "11.4.0"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc
url: "https://pub.dev"
source: hosted
version: "13.0.1"
version: "12.1.0"
permission_handler_apple:
dependency: transitive
description:
@@ -741,6 +733,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.0"
send_message:
dependency: "direct main"
description:
name: send_message
sha256: "79b5f69fd3ab0b9e6265f8d972800d7989b3082a0523c7f4b8e38bf4e1c71235"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
share_plus:
dependency: "direct main"
description:
@@ -841,7 +841,7 @@ packages:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
version: "0.0.0"
source_span:
dependency: transitive
description:
@@ -862,18 +862,18 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.4"
string_scanner:
dependency: transitive
description:
@@ -894,10 +894,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
version: "0.7.6"
typed_data:
dependency: transitive
description:
@@ -1006,10 +1006,10 @@ packages:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.1.4"
version: "2.2.0"
vm_service:
dependency: transitive
description:
@@ -1067,5 +1067,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.5.0 <4.0.0"
dart: ">=3.8.0-0 <4.0.0"
flutter: ">=3.24.0"

View File

@@ -57,14 +57,13 @@ dependencies:
share_plus: ^7.2.1
confetti: ^0.7.0
pdf: ^3.11.3
permission_handler: ^12.0.1
permission_handler: ^11.3.1
device_info_plus: ^11.3.0
showcaseview: ^2.0.3
package_info_plus: ^4.2.0
simcards: ^0.0.1
uuid: ^4.5.1
#send_message: ^1.0.0
flutter_sms: ^2.3.3
send_message: ^1.0.0
fluttertoast: ^8.2.6
# jailbreak_root_detection: "^1.1.6"