diff --git a/lib/features/profile/profile_screen.dart b/lib/features/profile/profile_screen.dart index 0d56ec5..f9b6fea 100644 --- a/lib/features/profile/profile_screen.dart +++ b/lib/features/profile/profile_screen.dart @@ -5,6 +5,7 @@ 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/features/profile/tpin/change_tpin_screen.dart'; import 'package:kmobile/security/secure_storage.dart'; import 'package:local_auth/local_auth.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -12,6 +13,9 @@ import 'package:shared_preferences/shared_preferences.dart'; import '../../di/injection.dart'; import '../../l10n/app_localizations.dart'; import 'package:kmobile/features/profile/preferences/preference_screen.dart'; +import 'package:kmobile/api/services/auth_service.dart'; +import 'package:kmobile/features/fund_transfer/screens/tpin_set_screen.dart'; + class ProfileScreen extends StatefulWidget { final String mobileNumber; @@ -190,11 +194,55 @@ class _ProfileScreenState extends State { }, ), ListTile( - leading: const Icon(Icons.password), - title: const Text("Change TPIN"), - onTap: () async { - }, - ), + title: Text('Change TPIN'), + onTap: () async { + // 1. Get the AuthService instance + final authService = getIt(); + + // 2. Call checkTpin() to see if TPIN is set + final isTpinSet = await authService.checkTpin(); + + // 3. If TPIN is not set, show the dialog + if (!isTpinSet) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('TPIN Not Set'), + content: Text('You have not set a TPIN yet. Please set a TPIN to proceed.'), + actions: [ + TextButton( + child: Text('Back'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: Text('Proceed'), + onPressed: () { + Navigator.of(context).pop(); // Dismiss the dialog + // Navigate to the TPIN set screen + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TpinSetScreen(), + ), + ); + }, + ), + ], + ); + }, + ); + } else { + // Case 2: TPIN is set +Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const ChangeTpinScreen(), + ), +); + } + }, +), // ListTile( // leading: const Icon(Icons.password), // title: const Text("Change Login MPIN"), diff --git a/lib/features/profile/tpin/change_tpin_otp_screen.dart b/lib/features/profile/tpin/change_tpin_otp_screen.dart index e69de29..96fa682 100644 --- a/lib/features/profile/tpin/change_tpin_otp_screen.dart +++ b/lib/features/profile/tpin/change_tpin_otp_screen.dart @@ -0,0 +1,53 @@ + + import 'package:flutter/material.dart'; + import 'package:kmobile/widgets/pin_input_field.dart'; // Use the new widget + + class ChangeTpinOtpScreen extends StatefulWidget { + final String newTpin; + const ChangeTpinOtpScreen({super.key, required this.newTpin}); + + @override + State createState() => _ChangeTpinOtpScreenState(); + } + + class _ChangeTpinOtpScreenState extends State { + final _otpController = TextEditingController(); + + void _handleVerifyOtp() { + // TODO: Add API call to verify OTP and change TPIN + print('Verifying OTP: ${_otpController.text} for new TPIN: ${widget.newTpin}'); + Navigator.of(context).popUntil((route) => route.isFirst); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Verify OTP'), + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 24), + const Text( + 'Enter the OTP sent to your registered mobile number.', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16), + ), + const SizedBox(height: 32), + PinInputField( + controller: _otpController, + ), + const SizedBox(height: 32), + ElevatedButton( + onPressed: _handleVerifyOtp, + child: const Text('Verify & Change TPIN'), + ), + ], + ), + ), + ); + } + } \ No newline at end of file diff --git a/lib/features/profile/tpin/change_tpin_screen.dart b/lib/features/profile/tpin/change_tpin_screen.dart index e69de29..912b5e2 100644 --- a/lib/features/profile/tpin/change_tpin_screen.dart +++ b/lib/features/profile/tpin/change_tpin_screen.dart @@ -0,0 +1,101 @@ + import 'package:flutter/material.dart'; + import 'package:kmobile/features/profile/tpin/change_tpin_otp_screen.dart'; + import 'package:kmobile/widgets/pin_input_field.dart'; // Use the new widget + + class ChangeTpinScreen extends StatefulWidget { + const ChangeTpinScreen({super.key}); + + @override + State createState() => _ChangeTpinScreenState(); + } + + class _ChangeTpinScreenState extends State { + final _formKey = GlobalKey(); + final _oldTpinController = TextEditingController(); + final _newTpinController = TextEditingController(); + final _confirmTpinController = TextEditingController(); + + @override + void dispose() { + _oldTpinController.dispose(); + _newTpinController.dispose(); + _confirmTpinController.dispose(); + super.dispose(); + } + + void _handleChangeTpin() { + if (_formKey.currentState!.validate()) { + // TODO: Add API call to request OTP for TPIN change + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ChangeTpinOtpScreen( + newTpin: _newTpinController.text, + ), + ), + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Change TPIN'), + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Current TPIN'), + const SizedBox(height: 8), + PinInputField( + controller: _oldTpinController, + validator: (value) { + if (value == null || value.length != 6) { + return 'Please enter your 6-digit old TPIN'; + } + return null; + }, + ), + const SizedBox(height: 24), + const Text('New TPIN'), + const SizedBox(height: 8), + PinInputField( + controller: _newTpinController, + validator: (value) { + if (value == null || value.length != 6) { + return 'Please enter a 6-digit new TPIN'; + } + 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), + ElevatedButton( + onPressed: _handleChangeTpin, + child: const Text('Proceed'), + ), + ], + ), + ), + ), + ); + } + } \ No newline at end of file diff --git a/lib/widgets/pin_input_field.dart b/lib/widgets/pin_input_field.dart new file mode 100644 index 0000000..f7016d3 --- /dev/null +++ b/lib/widgets/pin_input_field.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class PinInputField extends StatelessWidget { + final TextEditingController controller; + final int length; + final FormFieldValidator? validator; + + const PinInputField({ + super.key, + required this.controller, + this.length = 6, + this.validator, + }); + + @override + Widget build(BuildContext context) { + return FormField( + validator: validator, + builder: (FormFieldState state) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextField( + controller: controller, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(length), + ], + obscureText: true, + obscuringCharacter: '*', + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: 'Enter $length-digit PIN', + counterText: '', // Hide the counter + ), + onChanged: (value) { + state.didChange(value); + }, + ), + if (state.hasError) + Padding( + padding: const EdgeInsets.only(top: 8.0, left: 12.0), + child: Text( + state.errorText!, + style: TextStyle( + color: Theme.of(context).colorScheme.error, + fontSize: 12, + ), + ), + ), + ], + ); + }, + ); + } +} \ No newline at end of file