diff --git a/lib/api/services/change_password_service.dart b/lib/api/services/change_password_service.dart index df0143e..5d90bd4 100644 --- a/lib/api/services/change_password_service.dart +++ b/lib/api/services/change_password_service.dart @@ -22,6 +22,23 @@ class ChangePasswordService { return response.toString(); } + Future getOtpTpin({ + required String mobileNumber, + }) async { + final response = await _dio.post( + '/api/otp/send', + data: { + 'mobileNumber': mobileNumber, + 'type': "CHANGE_TPIN" + }, + ); + if (response.statusCode != 200) { + throw Exception("Invalid Mobile Number/Type"); + } + print(response.toString()); + return response.toString(); + } + Future validateOtp({ required String otp, diff --git a/lib/features/fund_transfer/screens/tpin_otp_screen.dart b/lib/features/fund_transfer/screens/tpin_otp_screen.dart index dd27817..859608e 100644 --- a/lib/features/fund_transfer/screens/tpin_otp_screen.dart +++ b/lib/features/fund_transfer/screens/tpin_otp_screen.dart @@ -1,6 +1,8 @@ import '../../../l10n/app_localizations.dart'; import 'package:flutter/material.dart'; import 'package:kmobile/features/fund_transfer/screens/tpin_set_screen.dart'; +import 'package:kmobile/api/services/change_password_service.dart'; +import 'package:kmobile/di/injection.dart'; class TpinOtpScreen extends StatefulWidget { const TpinOtpScreen({super.key}); @@ -10,11 +12,13 @@ class TpinOtpScreen extends StatefulWidget { } class _TpinOtpScreenState extends State { - final List _focusNodes = List.generate(4, (_) => FocusNode()); + final List _focusNodes = List.generate(6, (_) => FocusNode()); final List _controllers = List.generate( - 4, + 6, (_) => TextEditingController(), ); + bool _isLoading = false; + final ChangePasswordService _changePasswordService = getIt(); @override void dispose() { @@ -28,7 +32,7 @@ class _TpinOtpScreenState extends State { } void _onOtpChanged(int idx, String value) { - if (value.length == 1 && idx < 3) { + if (value.length == 1 && idx <5) { _focusNodes[idx + 1].requestFocus(); } if (value.isEmpty && idx > 0) { @@ -39,130 +43,162 @@ class _TpinOtpScreenState extends State { String get _enteredOtp => _controllers.map((c) => c.text).join(); - void _verifyOtp() { - if (_enteredOtp == '0000') { +void _verifyOtp() async { + setState(() { + _isLoading = true; + }); + + try { + // IMPORTANT: You may need to pass the mobile number here as well + await _changePasswordService.validateOtp( + otp: _enteredOtp, + mobileNumber: '8981274001', // Replace with actual mobile number + ); + + if (mounted) { Navigator.pushReplacement( context, - MaterialPageRoute(builder: (_) => TpinSetScreen()), + MaterialPageRoute(builder: (_) => const TpinSetScreen()), ); - } else { + } + } catch (e) { + if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(AppLocalizations.of(context).invalidOtp)), ); } - } - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Scaffold( - appBar: AppBar( - title: Text(AppLocalizations.of(context).enterOtp), - centerTitle: true, - elevation: 0, - ), - body: Center( - child: SingleChildScrollView( - child: Column( - // mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.lock_outline, - size: 48, - color: theme.colorScheme.primary, - ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context).otpVerification, - style: theme.textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.bold, - color: theme.colorScheme.primary, - ), - ), - const SizedBox(height: 8), - Text( - AppLocalizations.of(context).otpSentMessage, - textAlign: TextAlign.center, - style: theme.textTheme.bodyMedium?.copyWith( - color: Colors.grey[700], - ), - ), - const SizedBox(height: 32), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: List.generate(4, (i) { - return Container( - width: 48, - margin: const EdgeInsets.symmetric(horizontal: 8), - child: TextField( - controller: _controllers[i], - focusNode: _focusNodes[i], - keyboardType: TextInputType.number, - textAlign: TextAlign.center, - maxLength: 1, - obscureText: true, - obscuringCharacter: '⬤', - decoration: InputDecoration( - counterText: '', - filled: true, - fillColor: Theme.of(context).primaryColorLight, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide( - color: theme.colorScheme.primary, - width: 2, - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide( - color: theme.colorScheme.primary, - width: 2.5, - ), - ), - ), - onChanged: (val) => _onOtpChanged(i, val), - ), - ); - }), - ), - const SizedBox(height: 32), - ElevatedButton.icon( - icon: const Icon(Icons.verified_user_rounded), - label: Text( - AppLocalizations.of(context).verifyOtp, - style: const TextStyle( - fontSize: 18, fontWeight: FontWeight.w600), - ), - style: ElevatedButton.styleFrom( - backgroundColor: theme.colorScheme.onPrimary, - padding: const EdgeInsets.symmetric( - vertical: 14, - horizontal: 28, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30), - ), - ), - onPressed: _enteredOtp.length == 4 ? _verifyOtp : null, - ), - const SizedBox(height: 16), - TextButton( - onPressed: () { - // Resend OTP logic here - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(AppLocalizations.of(context).otpResent), - ), - ); - }, - child: Text(AppLocalizations.of(context).resendOtp), - ), - const SizedBox(height: 60), - ], - ), - ), - ), - ); + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } } } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context).enterOtp), + centerTitle: true, + elevation: 0, + ), + body: Center( + child: SingleChildScrollView( + child: Column( + children: [ + Icon( + Icons.lock_outline, + size: 48, + color: theme.colorScheme.primary, + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context).otpVerification, + style: theme.textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + color: theme.colorScheme.primary, + ), + ), + const SizedBox(height: 8), + Text( + AppLocalizations.of(context).otpSentMessage, + textAlign: TextAlign.center, + style: theme.textTheme.bodyMedium?.copyWith( + color: Colors.grey[700], + ), + ), + const SizedBox(height: 32), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(6, (i) { + return Container( + width: 32, + margin: const EdgeInsets.symmetric(horizontal: 8), + child: TextField( + controller: _controllers[i], + focusNode: _focusNodes[i], + keyboardType: TextInputType.number, + textAlign: TextAlign.center, + maxLength: 1, + obscureText: true, + obscuringCharacter: '*', + decoration: InputDecoration( + counterText: '', + filled: true, + fillColor: Theme.of(context).primaryColorLight, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: theme.colorScheme.primary, + width: 2, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: theme.colorScheme.primary, + width: 2.5, + ), + ), + ), + onChanged: (val) => _onOtpChanged(i, val), + ), + ); + }), + ), + const SizedBox(height: 32), + ElevatedButton.icon( + // Update icon to show a loading indicator + icon: _isLoading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2, + ), + ) + : const Icon(Icons.verified_user_rounded), + label: Text( + AppLocalizations.of(context).verifyOtp, + style: const TextStyle( + fontSize: 18, fontWeight: FontWeight.w600), + ), + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.onPrimary, + padding: const EdgeInsets.symmetric( + vertical: 14, + horizontal: 28, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(30), + ), + ), + // Update onPressed to handle loading state + onPressed: (_enteredOtp.length == 6 && !_isLoading) + ? _verifyOtp + : null, + ), + const SizedBox(height: 16), + TextButton( + onPressed: () { + // You can also add a getOtp call here for resending + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context).otpResent), + ), + ); + }, + child: Text(AppLocalizations.of(context).resendOtp), + ), + const SizedBox(height: 60), + ], + ), + ), + ), + ); + } + } diff --git a/lib/features/fund_transfer/screens/tpin_prompt_screen.dart b/lib/features/fund_transfer/screens/tpin_prompt_screen.dart index 35a2e4c..b546b9e 100644 --- a/lib/features/fund_transfer/screens/tpin_prompt_screen.dart +++ b/lib/features/fund_transfer/screens/tpin_prompt_screen.dart @@ -1,10 +1,48 @@ import '../../../l10n/app_localizations.dart'; import 'package:flutter/material.dart'; import 'package:kmobile/features/fund_transfer/screens/tpin_otp_screen.dart'; +import 'package:kmobile/api/services/change_password_service.dart'; // <-- Add this import +import 'package:kmobile/di/injection.dart'; // <-- Add this import -class TpinSetupPromptScreen extends StatelessWidget { +class TpinSetupPromptScreen extends StatefulWidget { const TpinSetupPromptScreen({super.key}); + @override + State createState() => _TpinSetupPromptScreenState(); +} + + class _TpinSetupPromptScreenState extends State { + // 3. Add state variables + bool _isLoading = false; + final ChangePasswordService _changePasswordService = getIt(); + Future _getOtp() async { + setState(() { + _isLoading = true; + }); + + try { + await _changePasswordService.getOtp(mobileNumber: '8981274001'); + if (mounted) { + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (_) => const TpinOtpScreen()), + ); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: ${e.toString()}')), + ); + } + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } +} + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -37,30 +75,33 @@ class TpinSetupPromptScreen extends StatelessWidget { ), ), const SizedBox(height: 32), - ElevatedButton.icon( - icon: const Icon(Icons.arrow_forward_rounded), - label: Text( - AppLocalizations.of(context).setTpin, - style: - const TextStyle(fontSize: 18, fontWeight: FontWeight.w600), - ), - style: ElevatedButton.styleFrom( - backgroundColor: theme.colorScheme.onPrimary, - padding: const EdgeInsets.symmetric( - vertical: 14, - horizontal: 32, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - onPressed: () { - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (_) => const TpinOtpScreen()), - ); - }, - ), +ElevatedButton.icon( + icon: _isLoading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2, + ), + ) + : const Icon(Icons.arrow_forward_rounded), + label: Text( + AppLocalizations.of(context).setTpin, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600), + ), + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.onPrimary, + padding: const EdgeInsets.symmetric( + vertical: 14, + horizontal: 32, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + onPressed: _isLoading ? null : _getOtp, // <-- Use the new function +), const SizedBox(height: 18), Padding( padding: const EdgeInsets.symmetric(horizontal: 18.0), @@ -77,4 +118,4 @@ class TpinSetupPromptScreen extends StatelessWidget { ), ); } -} +} \ No newline at end of file