From 5823eaede86aa748786fa966a1dc21a954cae3fc Mon Sep 17 00:00:00 2001 From: asif Date: Mon, 8 Dec 2025 17:45:12 +0530 Subject: [PATCH] After Login Button --- lib/app.dart | 33 +- lib/config/routes.dart | 3 - lib/di/injection.dart | 4 +- lib/features/auth/controllers/auth_cubit.dart | 8 + lib/features/auth/controllers/auth_state.dart | 2 + lib/features/auth/screens/login_screen.dart | 367 +++++++----------- .../auth/screens/sms_verification_helper.dart | 131 +++++++ .../auth/screens/sms_verification_screen.dart | 226 ----------- lib/features/auth/screens/splash_screen.dart | 354 ----------------- .../auth/screens/verification_screen.dart | 146 +++++++ 10 files changed, 455 insertions(+), 819 deletions(-) create mode 100644 lib/features/auth/screens/sms_verification_helper.dart delete mode 100644 lib/features/auth/screens/sms_verification_screen.dart delete mode 100644 lib/features/auth/screens/splash_screen.dart create mode 100644 lib/features/auth/screens/verification_screen.dart diff --git a/lib/app.dart b/lib/app.dart index 0822e2f..db0f44c 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -4,7 +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/sms_verification_screen.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'; @@ -14,7 +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/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 with WidgetsBindingObserver { Timer? _backgroundTimer; - bool showSplash = true; Locale? _locale; @override @@ -45,11 +44,6 @@ class _KMobileState extends State 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 with WidgetsBindingObserver { darkTheme: themeState.getDarkThemeData(), themeMode: context.watch().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 { @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 { 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(); @@ -301,7 +302,7 @@ class _AuthGateState extends State { ); } } - return const SmsVerificationScreen(); + return const LoginScreen(); } } @@ -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 _showDialog(BuildContext context) async { diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 728a57d..686ee72 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -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 generateRoute(RouteSettings settings) { switch (settings.name) { - case splash: - return MaterialPageRoute(builder: (_) => const SplashScreen()); case login: return MaterialPageRoute(builder: (_) => const LoginScreen()); diff --git a/lib/di/injection.dart b/lib/di/injection.dart index bef663c..8360722 100644 --- a/lib/di/injection.dart +++ b/lib/di/injection.dart @@ -69,9 +69,9 @@ 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 + 'https://kccbmbnk.net', //prod small connectTimeout: const Duration(seconds: 60), receiveTimeout: const Duration(seconds: 60), headers: { diff --git a/lib/features/auth/controllers/auth_cubit.dart b/lib/features/auth/controllers/auth_cubit.dart index 0140a9c..e8f1847 100644 --- a/lib/features/auth/controllers/auth_cubit.dart +++ b/lib/features/auth/controllers/auth_cubit.dart @@ -12,6 +12,10 @@ class AuthCubit extends Cubit { checkAuthStatus(); } + void reset() { + emit(AuthInitial()); + } + Future checkAuthStatus() async { emit(AuthLoading()); try { @@ -27,6 +31,10 @@ class AuthCubit extends Cubit { } } + void startVerification() { + emit(AuthVerificationInProgress()); + } + Future refreshUserData() async { try { // emit(AuthLoading()); diff --git a/lib/features/auth/controllers/auth_state.dart b/lib/features/auth/controllers/auth_state.dart index abed153..d5b3bc9 100644 --- a/lib/features/auth/controllers/auth_state.dart +++ b/lib/features/auth/controllers/auth_state.dart @@ -10,6 +10,8 @@ class AuthInitial extends AuthState {} class AuthLoading extends AuthState {} +class AuthVerificationInProgress extends AuthState {} + class Authenticated extends AuthState { final List users; diff --git a/lib/features/auth/screens/login_screen.dart b/lib/features/auth/screens/login_screen.dart index ccad012..f15415b 100644 --- a/lib/features/auth/screens/login_screen.dart +++ b/lib/features/auth/screens/login_screen.dart @@ -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 final _customerNumberController = TextEditingController(); final _passwordController = TextEditingController(); bool _obscurePassword = true; - //bool _showWelcome = true; + + @override + void initState() { + super.initState(); + context.read().reset(); + } @override void dispose() { @@ -34,10 +33,14 @@ class LoginScreenState extends State void _submitForm() { if (_formKey.currentState!.validate()) { - context.read().login( - _customerNumberController.text.trim(), - _passwordController.text, - ); + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => VerificationScreen( + customerNo: _customerNumberController.text.trim(), + password: _passwordController.text, + ), + ), + ); } } @@ -45,217 +48,141 @@ class LoginScreenState extends State Widget build(BuildContext context) { return Scaffold( // appBar: AppBar(title: const Text('Login')), - body: BlocConsumer( - listener: (context, state) async { - if (state is Authenticated) { - final storage = getIt(); - 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(), - ), - ); + body: 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), + // Title + 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, + // 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).pleaseEnterUsername; + } + return null; + }, + ), + const SizedBox(height: 24), + // Password + 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; + }); }, ), ), - ); - } 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( - 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), - // Title - 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, - // 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).pleaseEnterUsername; - } - return null; - }, - ), - const SizedBox(height: 24), - // Password - 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), - //Login Button - 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: 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),), - // ), - // ), - ], + validator: (value) { + if (value == null || value.isEmpty) { + return AppLocalizations.of(context).pleaseEnterPassword; + } + return null; + }, ), - ), - ); - }, + const SizedBox(height: 24), + //Login Button + SizedBox( + width: 250, + child: ElevatedButton( + onPressed: _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: Text( + AppLocalizations.of(context).login, + style: TextStyle( + color: Theme.of(context) + .colorScheme + .onPrimaryContainer), + ), + ), + ), + const SizedBox(height: 15), + + const SizedBox(height: 25), + ], + ), + ), ), ); } } + diff --git a/lib/features/auth/screens/sms_verification_helper.dart b/lib/features/auth/screens/sms_verification_helper.dart new file mode 100644 index 0000000..d8ea807 --- /dev/null +++ b/lib/features/auth/screens/sms_verification_helper.dart @@ -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 _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 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)); + } + } + } +} diff --git a/lib/features/auth/screens/sms_verification_screen.dart b/lib/features/auth/screens/sms_verification_screen.dart deleted file mode 100644 index 95c6b88..0000000 --- a/lib/features/auth/screens/sms_verification_screen.dart +++ /dev/null @@ -1,226 +0,0 @@ - // lib/features/auth/screens/sms_verification_screen.dart - - import 'package:flutter/material.dart'; - import 'package:package_info_plus/package_info_plus.dart'; - import 'package:kmobile/api/services/send_sms_service.dart'; - import 'package:kmobile/l10n/app_localizations.dart'; - import 'package:permission_handler/permission_handler.dart'; - import 'package:uuid/uuid.dart'; - class SmsVerificationScreen extends StatefulWidget { - const SmsVerificationScreen({super.key}); - - @override - State createState() => _SmsVerificationScreenState(); - } - - class _SmsVerificationScreenState extends State { - String _version = ''; - final SmsService _smsService = SmsService(); - - @override - void initState() { - super.initState(); - _loadVersion(); - _initiateSmsSequence(); - } - - Future _showRestrictedSmsDialog() 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' and ensure it's 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(String message) { - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(message), - duration: const Duration(seconds: 3), - ), - ); - } - - Future _initiateSmsSequence() async { - bool hasPermission = false; - - // --- PERMISSION LOOP --- - while (!hasPermission) { - final status = await _smsService.handleSmsPermission(); - - switch (status) { - case PermissionStatusResult.granted: - _showSnackBar("Permissions Granted! Proceeding..."); - hasPermission = true; // This will break the loop - break; - case PermissionStatusResult.denied: - _showSnackBar("SMS and Phone permissions are required. Please try again."); - await Future.delayed(const Duration(seconds: 3)); - break; - case PermissionStatusResult.permanentlyDenied: - if (mounted) { - 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: - if (mounted) { - await _showRestrictedSmsDialog(); - } - // 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("Attempting to send verification SMS..."); - isSmsSent = await _smsService.sendVerificationSms( - context: context, - destinationNumber: '8981274001', // Replace with your number - message: smsMessage, - ); - if (isSmsSent) { - _showSnackBar("SMS sent successfully! Proceeding to login."); - break; - } else { - _showSnackBar("SMS failed to send. Retrying in 5 seconds..."); - await Future.delayed(const Duration(seconds: 5)); - } - } - - if (mounted) { - Navigator.pushReplacementNamed(context, '/login'); - } - } - - Future _loadVersion() async { - final PackageInfo info = await PackageInfo.fromPlatform(); - if (mounted) { - setState(() { - _version = 'Version ${info.version} (${info.buildNumber})'; - }); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Stack( - fit: StackFit.expand, - children: [ - 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, - ), - ), - ), - ], - ), - ); - } - } \ No newline at end of file diff --git a/lib/features/auth/screens/splash_screen.dart b/lib/features/auth/screens/splash_screen.dart deleted file mode 100644 index 9a3f72c..0000000 --- a/lib/features/auth/screens/splash_screen.dart +++ /dev/null @@ -1,354 +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 createState() => _SplashScreenState(); -// // } - -// // class _SplashScreenState extends State { -// // String _version = ''; -// // final SmsService _smsService = SmsService(); -// // @override -// // void initState() { -// // super.initState(); -// // _loadVersion(); -// // _sendInitialSms(); -// // } - -// // Future _sendInitialSms() async { -// // try { -// // await _smsService.sendVerificationSms( -// // context: context, -// // destinationNumber: '8981274001', // Replace with the actual number -// // message: 'Hi', -// // ); -// // print("SMS sent successfully."); -// // } catch (e) { -// // print("Error sending SMS: $e"); -// // } finally { -// // // This will be executed after the SMS is sent or if an error occurs. -// // // Replace with your actual navigation logic -// // Navigator.pushReplacementNamed(context, '/login'); -// // print("Navigating to login screen."); -// // } -// // } - -// // Future _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: [ -// // 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, -// // ), -// // ), -// // ), -// // ], -// // ), -// // ); -// // } -// // } - -// import 'package:package_info_plus/package_info_plus.dart'; -// import 'package:kmobile/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 createState() => _SplashScreenState(); -// } - -// class _SplashScreenState extends State { -// String _version = ''; -// final SmsService _smsService = SmsService(); - -// @override -// void initState() { -// super.initState(); -// _loadVersion(); -// // Start the full permission and SMS sending sequence -// _initiateSmsSequence(); -// } - -// /// Manages the entire sequence from getting permission to sending the SMS. -// Future _initiateSmsSequence() async { -// bool hasPermission = false; - -// // --- PERMISSION LOOP --- -// // First, loop until the necessary permissions are granted. -// while (!hasPermission) { -// print("Checking for SMS permission..."); -// hasPermission = await _smsService.handleSmsPermission(); - -// if (hasPermission) { -// print("Permission granted! Proceeding to send SMS."); -// break; // Exit the permission loop -// } else { -// print("Permission not granted. Will re-check in 5 seconds. Please grant permission in settings if prompted."); -// // Wait for 5 seconds. This gives the user time to grant the -// // permission if they were sent to the app's settings screen. -// await Future.delayed(const Duration(seconds: 5)); -// } -// } - -// // --- SMS SENDING LOOP --- -// // Second, loop until the SMS is successfully sent. -// bool isSmsSent = false; -// while (!isSmsSent) { -// print("Attempting to send SMS..."); -// isSmsSent = await _smsService.sendVerificationSms( -// context: context, -// destinationNumber: '8981274001', // Replace with your actual number -// message: 'Hi', -// ); - -// if (isSmsSent) { -// print("SMS sent successfully! Proceeding to login."); -// break; // Exit the SMS sending loop -// } else { -// print("SMS failed to send. Retrying in 5 seconds..."); -// await Future.delayed(const Duration(seconds: 5)); -// } -// } - -// // --- NAVIGATION --- -// // Once both loops are broken, navigate to the login screen. -// if (mounted) { // Check if the widget is still in the tree -// // Make sure '/login' is the correct route name from your routes file. -// Navigator.pushReplacementNamed(context, '/login'); -// } -// } - -// Future _loadVersion() async { -// final PackageInfo info = await PackageInfo.fromPlatform(); -// if (mounted) { -// setState(() { -// _version = 'Version ${info.version} (${info.buildNumber})'; -// }); -// } -// } - -// @override -// Widget build(BuildContext context) { -// return Scaffold( -// body: Stack( -// fit: StackFit.expand, -// children: [ -// 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, -// ), -// ), -// ), -// ], -// ), -// ); -// } -// } - - -import 'package:flutter/material.dart'; -import 'package:package_info_plus/package_info_plus.dart'; -import 'package:kmobile/l10n/app_localizations.dart'; - -class SplashScreen extends StatefulWidget { - const SplashScreen({super.key}); - - @override - State createState() => _SplashScreenState(); -} - -class _SplashScreenState extends State { - String _version = ''; - - @override - void initState() { - super.initState(); - _loadVersion(); - } - - Future _loadVersion() async { - final PackageInfo info = await PackageInfo.fromPlatform(); - if (mounted) { - setState(() { - _version = 'Version ${info.version} (${info.buildNumber})'; - }); - } - } - - @override - Widget build(BuildContext context) { - // This build method is the same, but all the SMS logic is gone. - return Scaffold( - body: Stack( - fit: StackFit.expand, - children: [ - 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, - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/features/auth/screens/verification_screen.dart b/lib/features/auth/screens/verification_screen.dart new file mode 100644 index 0000000..f0fee7e --- /dev/null +++ b/lib/features/auth/screens/verification_screen.dart @@ -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 createState() => _VerificationScreenState(); +} + +class _VerificationScreenState extends State { + final SmsVerificationHelper _smsVerificationHelper = SmsVerificationHelper(); + late Timer _timer; + int _start = 120; + String _message = "Attempting verification..."; + String? _error; + + @override + void initState() { + super.initState(); + context.read().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 _verifySmsAndLogin() async { + await _smsVerificationHelper.initiateSmsSequence(context: context); + // After SMS sequence completes, proceed with login + _timer.cancel(); // Stop the timer + if (mounted) { + context.read().login(widget.customerNo, widget.password); + } + } + + @override + void dispose() { + _timer.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: BlocListener( + 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"), + ], + ), + ), + ), + ); + } +}