initial commit: push everything
This commit is contained in:
343
lib/views/onboardscreens/Mpin.dart
Normal file
343
lib/views/onboardscreens/Mpin.dart
Normal file
@@ -0,0 +1,343 @@
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user