Change TPIn #2
This commit is contained in:
@@ -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/change_password/change_password_screen.dart';
|
||||||
import 'package:kmobile/features/profile/daily_transaction_limit.dart';
|
import 'package:kmobile/features/profile/daily_transaction_limit.dart';
|
||||||
import 'package:kmobile/features/profile/logout_dialog.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:kmobile/security/secure_storage.dart';
|
||||||
import 'package:local_auth/local_auth.dart';
|
import 'package:local_auth/local_auth.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.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 '../../di/injection.dart';
|
||||||
import '../../l10n/app_localizations.dart';
|
import '../../l10n/app_localizations.dart';
|
||||||
import 'package:kmobile/features/profile/preferences/preference_screen.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 {
|
class ProfileScreen extends StatefulWidget {
|
||||||
final String mobileNumber;
|
final String mobileNumber;
|
||||||
@@ -190,9 +194,53 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.password),
|
title: Text('Change TPIN'),
|
||||||
title: const Text("Change TPIN"),
|
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
// 1. Get the AuthService instance
|
||||||
|
final authService = getIt<AuthService>();
|
||||||
|
|
||||||
|
// 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: <Widget>[
|
||||||
|
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(
|
// ListTile(
|
||||||
|
|||||||
@@ -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<ChangeTpinOtpScreen> createState() => _ChangeTpinOtpScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChangeTpinOtpScreenState extends State<ChangeTpinOtpScreen> {
|
||||||
|
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'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ChangeTpinScreen> createState() => _ChangeTpinScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChangeTpinScreenState extends State<ChangeTpinScreen> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
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'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
58
lib/widgets/pin_input_field.dart
Normal file
58
lib/widgets/pin_input_field.dart
Normal file
@@ -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<String>? validator;
|
||||||
|
|
||||||
|
const PinInputField({
|
||||||
|
super.key,
|
||||||
|
required this.controller,
|
||||||
|
this.length = 6,
|
||||||
|
this.validator,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FormField<String>(
|
||||||
|
validator: validator,
|
||||||
|
builder: (FormFieldState<String> 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user