Files
Xpress-rider/lib/views/onboardscreens/Mpin.dart

344 lines
12 KiB
Dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:nearle/views/helpers/constants/Colorconstants.dart';
import 'package:nearle/views/helpers/constants/Font_constant.dart';
import 'package:nearle/views/onboardscreens/Sign_in.dart';
import 'package:nearle/views/onboardscreens/otp_page.dart';
import 'package:nearle/widget/Bottom_page.dart';
import 'package:nearle/controllers/auth.dart';
import 'package:nearle/views/onboardscreens/signin_banner.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/services.dart';
class Mpin extends GetResponsiveView {
Mpin({super.key});
@override
Widget builder() {
return const _MpinView();
}
}
class _MpinView extends StatefulWidget {
const _MpinView();
@override
State<_MpinView> createState() => _MpinViewState();
}
class _MpinViewState extends State<_MpinView> {
final AuthController _auth = Get.put(AuthController());
final List<TextEditingController> _mpinControllers = List.generate(
4,
(_) => TextEditingController(),
);
final List<FocusNode> _focusNodes = List.generate(4, (_) => FocusNode());
bool isVerifying = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_focusNodes[0].requestFocus();
_maybeShowMasterPinReminder();
});
}
@override
void dispose() {
for (var c in _mpinControllers) {
c.dispose();
}
for (var f in _focusNodes) {
f.dispose();
}
super.dispose();
}
void _onMpinChange(String value, int index) {
if (value.isNotEmpty && index < 3) {
_focusNodes[index + 1].requestFocus();
} else if (value.isEmpty && index > 0) {
_focusNodes[index - 1].requestFocus();
}
final filled = _mpinControllers.every((c) => c.text.isNotEmpty);
if (filled) {
FocusScope.of(context).unfocus();
_submitMpin(); // auto-verify when 4 digits are filled
}
setState(() {});
}
String getMpin() => _mpinControllers.map((c) => c.text).join();
bool get isMpinFilled => _mpinControllers.every((c) => c.text.isNotEmpty);
void _clearMpinAndFocus() {
for (final c in _mpinControllers) {
c.clear();
}
if (_focusNodes.isNotEmpty) {
FocusScope.of(context).requestFocus(_focusNodes[0]);
}
setState(() {});
}
Future<void> _submitMpin() async {
if (!mounted || isVerifying || !isMpinFilled) return;
// Register retry callback so AuthController bottom-sheet "Retry" button
// can clear MPIN boxes and bring back keyboard when PIN is wrong.
_auth.onPinRetry = _clearMpinAndFocus;
setState(() => isVerifying = true);
final mpin = getMpin();
final ok = await _auth.verifyPinWithServer(mpin);
if (!mounted) return;
setState(() => isVerifying = false);
if (ok) {
// ✅ Wait a moment to ensure onduty is saved, then read it
await Future.delayed(const Duration(milliseconds: 100));
final prefs = await SharedPreferences.getInstance();
final onduty = prefs.getInt('onduty') ?? 0;
debugPrint('[MPIN] After verification - onduty=$onduty');
// Navigate based on onduty value
if (onduty == 0) {
debugPrint('[MPIN] Navigating to Introscreen (onduty=0)');
Get.offAll(() => SigninBanner());
} else {
debugPrint('[MPIN] Navigating to BottomPage (onduty=1)');
Get.offAll(() => const BottomPage());
}
} else {
// Wrong MPIN: show alert/snackbar and keep user on MPIN screen
}
}
Future<void> _maybeShowMasterPinReminder() async {
try {
final prefs = await SharedPreferences.getInstance();
final forceMasterPin =
prefs.getBool(AuthController.forceMasterPinPrefKey) ?? false;
if (forceMasterPin) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Use ${AuthController.masterPinValue} as PIN to continue.',
),
duration: const Duration(seconds: 4),
behavior: SnackBarBehavior.floating,
),
);
}
} catch (_) {}
}
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final height = size.height;
final width = size.width;
double scale = 1.0;
if (Get.width < 380) scale = 0.9;
if (Get.width > 800) scale = 1.2;
return Scaffold(
backgroundColor: Colors.white,
body: Stack(
children: [
SafeArea(
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: width * 0.05),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(height: height * 0.04),
SizedBox(
height: height * 0.25 * scale,
width: width * 0.6,
child: Image.asset(
"assets/images/Mpin.png",
fit: BoxFit.contain,
),
),
SizedBox(height: height * 0.04),
Text(
"Enter Your MPIN",
style: TextStyle(
fontSize: height * 0.05 * scale,
fontWeight: FontWeight.bold,
color: ColorConstants.primaryColor,
fontFamily: FontConstants.fontFamily,
),
),
SizedBox(height: height * 0.01),
Text(
"Access your account securely",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: height * 0.024 * scale,
color: Colors.black54,
fontFamily: FontConstants.fontFamily,
),
),
SizedBox(height: height * 0.06),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(4, (index) {
final isFilled = _mpinControllers[index].text.isNotEmpty;
return SizedBox(
width: 50 * scale,
height: 55 * scale,
child: RawKeyboardListener(
focusNode: FocusNode(),
onKey: (event) {
if (event is RawKeyDownEvent &&
event.logicalKey ==
LogicalKeyboardKey.backspace &&
_mpinControllers[index].text.isEmpty &&
index > 0) {
_mpinControllers[index - 1].clear();
_focusNodes[index - 1].requestFocus();
setState(() {});
}
},
child: TextField(
controller: _mpinControllers[index],
focusNode: _focusNodes[index],
textAlign: TextAlign.center,
textAlignVertical: TextAlignVertical.center,
obscureText: true,
keyboardType: TextInputType.number,
maxLength: 1,
style: TextStyle(
fontSize: height * 0.028 * scale,
fontWeight: FontWeight.bold,
color: isFilled ? Colors.white : Colors.black,
),
decoration: InputDecoration(
counterText: "",
contentPadding: EdgeInsets.zero,
filled: true,
fillColor: isFilled
? ColorConstants.primaryColor
: Colors.transparent,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8 * scale),
borderSide: const BorderSide(
color: Colors.grey,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8 * scale),
borderSide: const BorderSide(
color: Color(0xFF662582),
width: 2,
),
),
),
onChanged: (value) => _onMpinChange(value, index),
),
),
);
}),
),
SizedBox(height: height * 0.02),
// Retry + Forget Pin row
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
onPressed: () {
// Clear all MPIN boxes and focus first, bringing up keyboard
for (final c in _mpinControllers) {
c.clear();
}
if (_focusNodes.isNotEmpty) {
FocusScope.of(context).requestFocus(_focusNodes[0]);
}
setState(() {});
},
child: Text(
"",
style: TextStyle(
fontSize: height * 0.022 * scale,
fontWeight: FontWeight.w600,
color: Colors.black87,
fontFamily: FontConstants.fontFamily,
),
),
),
InkWell(
onTap: () async {
await _auth.sendOtp();
Get.to(OtpPage());
},
child: Transform.translate(
offset: Offset(-18, 0),
child: Text(
"Forget Pin?",
style: TextStyle(
fontSize: height * 0.025 * scale,
fontWeight: FontWeight.bold,
color: ColorConstants.primaryColor,
fontFamily: FontConstants.fontFamily,
decoration: TextDecoration.underline,
),
),
),
),
],
),
],
),
),
),
Positioned(
top: height * 0.05,
left: width * 0.04,
child: InkWell(
onTap: () {
Get.to(SignIn());
},
borderRadius: BorderRadius.circular(30),
child: Container(
padding: EdgeInsets.all(width * 0.02),
decoration: const BoxDecoration(
color: Colors.black12,
shape: BoxShape.circle,
),
child: Icon(
Icons.arrow_back,
color: Colors.black,
size: width * 0.06,
),
),
),
),
],
),
);
}
}