Change TPIn #4

This commit is contained in:
2025-11-08 20:07:04 +05:30
parent c26cc507a1
commit b5b6c6ed49
3 changed files with 171 additions and 189 deletions

View File

@@ -194,6 +194,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
}, },
), ),
ListTile( ListTile(
leading: const Icon(Icons.password),
title: Text('Change TPIN'), title: Text('Change TPIN'),
onTap: () async { onTap: () async {
// 1. Get the AuthService instance // 1. Get the AuthService instance

View File

@@ -1,17 +1,16 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:kmobile/di/injection.dart'; import 'package:kmobile/di/injection.dart';
import 'package:kmobile/features/dashboard/screens/dashboard_screen.dart';
import 'package:kmobile/widgets/pin_input_field.dart'; import 'package:kmobile/widgets/pin_input_field.dart';
import '../../../api/services/change_password_service.dart'; import '../../../api/services/change_password_service.dart';
class ChangeTpinOtpScreen extends StatefulWidget { class ChangeTpinOtpScreen extends StatefulWidget {
final String oldTpin;
final String newTpin; final String newTpin;
final String mobileNumber; // Receive mobile number final String mobileNumber;
const ChangeTpinOtpScreen({ const ChangeTpinOtpScreen({
super.key, super.key,
required this.oldTpin,
required this.newTpin, required this.newTpin,
required this.mobileNumber, required this.mobileNumber,
}); });
@@ -22,7 +21,8 @@
class _ChangeTpinOtpScreenState extends State<ChangeTpinOtpScreen> { class _ChangeTpinOtpScreenState extends State<ChangeTpinOtpScreen> {
final _otpController = TextEditingController(); final _otpController = TextEditingController();
final ChangePasswordService _changePasswordService = getIt<ChangePasswordService>(); final ChangePasswordService _changePasswordService =
getIt<ChangePasswordService>();
bool _isLoading = false; bool _isLoading = false;
void _handleVerifyOtp() async { void _handleVerifyOtp() async {
@@ -37,44 +37,43 @@
_isLoading = true; _isLoading = true;
}); });
String message = 'An unknown error occurred.';
bool success = false;
try { try {
// 1. Validate the OTP // 1. Validate the OTP first.
final responseString = await _changePasswordService.validateOtp( await _changePasswordService.validateOtp(
otp: _otpController.text, otp: _otpController.text,
mobileNumber: widget.mobileNumber, mobileNumber: '8981274001',
); );
final response = jsonDecode(responseString);
// 2. Check status and set message // 2. If OTP is valid, then call validateChangeTpin.
if (response['statusCode'] == 200 || response['statusCode'] == 2000) { await _changePasswordService.validateChangeTpin(
message = 'TPIN changed successfully!'; oldTpin: widget.oldTpin,
success = true; newTpin: widget.newTpin,
} else { );
message = response['message'] ?? 'Invalid OTP. Please try again.';
// 3. Show success message.
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('TPIN changed successfully!'),
backgroundColor: Colors.green,
),
);
// 4. Navigate back to the profile screen or home.
Navigator.of(context).popUntil((route) => route.isFirst);
} }
} catch (e) { } catch (e) {
message = 'Failed to verify OTP: $e';
} finally {
// 3. Show feedback
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(message), content: Text('An error occurred: $e'),
backgroundColor: success ? Colors.green : Colors.red, backgroundColor: Colors.red,
), ),
); );
}
// 4. Navigate to dashboard after 5 seconds } finally {
Timer(const Duration(seconds: 5), () { if (mounted) {
if (mounted) { setState(() {
Navigator.of(context).pushAndRemoveUntil( _isLoading = false;
MaterialPageRoute(builder: (context) => const DashboardScreen()),
(Route<dynamic> route) => false,
);
}
}); });
} }
} }

View File

@@ -1,164 +1,146 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:kmobile/di/injection.dart'; import 'package:kmobile/di/injection.dart';
import 'package:kmobile/features/profile/tpin/change_tpin_otp_screen.dart'; import 'package:kmobile/features/profile/tpin/change_tpin_otp_screen.dart';
import 'package:kmobile/widgets/pin_input_field.dart'; import 'package:kmobile/widgets/pin_input_field.dart';
import '../../../api/services/change_password_service.dart'; import '../../../api/services/change_password_service.dart';
import 'dart:convert';
class ChangeTpinScreen extends StatefulWidget { class ChangeTpinScreen extends StatefulWidget {
final String mobileNumber; final String mobileNumber;
const ChangeTpinScreen({super.key, required this.mobileNumber}); const ChangeTpinScreen({super.key, required this.mobileNumber});
@override @override
State<ChangeTpinScreen> createState() => _ChangeTpinScreenState(); State<ChangeTpinScreen> createState() => _ChangeTpinScreenState();
} }
class _ChangeTpinScreenState extends State<ChangeTpinScreen> { class _ChangeTpinScreenState extends State<ChangeTpinScreen> {
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
final _oldTpinController = TextEditingController(); final _oldTpinController = TextEditingController();
final _newTpinController = TextEditingController(); final _newTpinController = TextEditingController();
final _confirmTpinController = TextEditingController(); final _confirmTpinController = TextEditingController();
final ChangePasswordService _changePasswordService = getIt<ChangePasswordService>(); final ChangePasswordService _changePasswordService =
bool _isLoading = false; getIt<ChangePasswordService>();
bool _isLoading = false;
@override @override
void dispose() { void dispose() {
_oldTpinController.dispose(); _oldTpinController.dispose();
_newTpinController.dispose(); _newTpinController.dispose();
_confirmTpinController.dispose(); _confirmTpinController.dispose();
super.dispose(); super.dispose();
} }
void _handleChangeTpin() async { void _handleChangeTpin() async {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
setState(() { setState(() {
_isLoading = true; _isLoading = true;
}); });
try { try {
// 1. Validate the current TPIN and new TPIN // 1. Get OTP for TPIN change.
final responseString = await _changePasswordService.validateChangeTpin( await _changePasswordService.getOtpTpin(mobileNumber: '8981274001');
oldTpin: _oldTpinController.text,
newTpin: _newTpinController.text,
);
// The service throws an exception for non-200 HTTP status, // 2. Navigate to the OTP screen on success.
// so we assume HTTP 200 here and check the body. if (mounted) {
final response = jsonDecode(responseString); Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ChangeTpinOtpScreen(
oldTpin: _oldTpinController.text,
newTpin: _newTpinController.text,
mobileNumber: '8981274001',
),
),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to send OTP: $e')),
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
}
// 2. Check the status code from the response body @override
if (response.statusCode == 200) { Widget build(BuildContext context) {
// 3. Get OTP for TPIN change return Scaffold(
await _changePasswordService.getOtpTpin(mobileNumber: '8981274001'); appBar: AppBar(
title: const Text('Change TPIN'),
// 4. Navigate to OTP screen ),
if (mounted) { body: SingleChildScrollView(
Navigator.of(context).push( padding: const EdgeInsets.all(16.0),
MaterialPageRoute( child: Form(
builder: (context) => ChangeTpinOtpScreen( key: _formKey,
newTpin: _newTpinController.text, child: Column(
mobileNumber: '8981274001', // Pass mobile number crossAxisAlignment: CrossAxisAlignment.start,
), children: [
), const Text('Current TPIN'),
); const SizedBox(height: 8),
} PinInputField(
} else { controller: _oldTpinController,
if (mounted) { validator: (value) {
ScaffoldMessenger.of(context).showSnackBar( if (value == null || value.length != 6) {
SnackBar(content: Text(response['message'] ?? 'Invalid TPIN details') return 'Please enter your 6-digit old TPIN';
)); }
} return null;
} },
} catch (e) { ),
if (mounted) { const SizedBox(height: 24),
ScaffoldMessenger.of(context).showSnackBar( const Text('New TPIN'),
SnackBar(content: Text('An error occurred: $e')), const SizedBox(height: 8),
); PinInputField(
} controller: _newTpinController,
} finally { validator: (value) {
if (mounted) { if (value == null || value.length != 6) {
setState(() { return 'Please enter a 6-digit new TPIN';
_isLoading = false; }
}); if (value == _oldTpinController.text) {
} return 'New TPIN must be different from the old one.';
} }
} return null;
} },
),
@override const SizedBox(height: 24),
Widget build(BuildContext context) { const Text('Confirm New TPIN'),
return Scaffold( const SizedBox(height: 8),
appBar: AppBar( PinInputField(
title: const Text('Change TPIN'), controller: _confirmTpinController,
), validator: (value) {
body: SingleChildScrollView( if (value == null || value.length != 6) {
padding: const EdgeInsets.all(16.0), return 'Please confirm your new TPIN';
child: Form( }
key: _formKey, if (value != _newTpinController.text) {
child: Column( return 'TPINs do not match';
crossAxisAlignment: CrossAxisAlignment.start, }
children: [ return null;
const Text('Current TPIN'), },
const SizedBox(height: 8), ),
PinInputField( const SizedBox(height: 32),
controller: _oldTpinController, SizedBox(
validator: (value) { width: double.infinity,
if (value == null || value.length != 6) { child: ElevatedButton(
return 'Please enter your 6-digit old TPIN'; onPressed: _isLoading ? null : _handleChangeTpin,
} child: _isLoading
return null; ? const SizedBox(
}, height: 24,
), width: 24,
const SizedBox(height: 24), child: CircularProgressIndicator(
const Text('New TPIN'), color: Colors.white,
const SizedBox(height: 8), strokeWidth: 2.5,
PinInputField( ),
controller: _newTpinController, )
validator: (value) { : const Text('Proceed'),
if (value == null || value.length != 6) { ),
return 'Please enter a 6-digit new TPIN'; ),
} ],
if (value == _oldTpinController.text) { ),
return 'New TPIN must be different from the old one.'; ),
} ),
return null; );
}, }
), }
const SizedBox(height: 24),
const Text('Confirm New TPIN'),
const SizedBox(height: 8),
PinInputField(
controller: _confirmTpinController,
validator: (value) {
if (value == null || value.length != 6) {
return 'Please confirm your new TPIN';
}
if (value != _newTpinController.text) {
return 'TPINs do not match';
}
return null;
},
),
const SizedBox(height: 32),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _isLoading ? null : _handleChangeTpin,
child: _isLoading
? const SizedBox(
height: 24,
width: 24,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2.5,
),
)
: const Text('Proceed'),
),
),
],
),
),
),
);
}
}