api integration
This commit is contained in:
@@ -1,24 +1,72 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:kmobile/features/auth/controllers/auth_cubit.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
import '../../../app.dart';
|
||||
import 'package:kmobile/app.dart';
|
||||
import 'package:kmobile/di/injection.dart';
|
||||
import 'package:kmobile/security/secure_storage.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
|
||||
enum MPinMode { enter, set, confirm }
|
||||
|
||||
class MPinScreen extends StatefulWidget {
|
||||
const MPinScreen({super.key});
|
||||
final MPinMode mode;
|
||||
final String? initialPin;
|
||||
final void Function(String pin)? onCompleted;
|
||||
|
||||
const MPinScreen({
|
||||
super.key,
|
||||
required this.mode,
|
||||
this.initialPin,
|
||||
this.onCompleted,
|
||||
});
|
||||
|
||||
@override
|
||||
MPinScreenState createState() => MPinScreenState();
|
||||
State<MPinScreen> createState() => _MPinScreenState();
|
||||
}
|
||||
|
||||
class MPinScreenState extends State<MPinScreen> {
|
||||
class _MPinScreenState extends State<MPinScreen> {
|
||||
List<String> mPin = [];
|
||||
String? errorText;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.mode == MPinMode.enter) {
|
||||
_tryBiometricBeforePin();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _tryBiometricBeforePin() async {
|
||||
final storage = getIt<SecureStorage>();
|
||||
final enabled = await storage.read('biometric_enabled');
|
||||
log('biometric_enabled: $enabled');
|
||||
if (enabled) {
|
||||
final auth = LocalAuthentication();
|
||||
if (await auth.canCheckBiometrics) {
|
||||
final didAuth = await auth.authenticate(
|
||||
localizedReason: 'Authenticate to access kMobile',
|
||||
options: const AuthenticationOptions(biometricOnly: true),
|
||||
);
|
||||
if (didAuth && mounted) {
|
||||
// success → directly “complete” your flow
|
||||
widget.onCompleted?.call('');
|
||||
// or navigate yourself:
|
||||
// Navigator.of(context).pushReplacement(
|
||||
// MaterialPageRoute(builder: (_) => const NavigationScaffold()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addDigit(String digit) {
|
||||
if (mPin.length < 4) {
|
||||
setState(() {
|
||||
mPin.add(digit);
|
||||
errorText = null;
|
||||
});
|
||||
if (mPin.length == 4) {
|
||||
_handleComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,10 +74,63 @@ class MPinScreenState extends State<MPinScreen> {
|
||||
if (mPin.isNotEmpty) {
|
||||
setState(() {
|
||||
mPin.removeLast();
|
||||
errorText = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleComplete() async {
|
||||
final pin = mPin.join();
|
||||
final storage = SecureStorage();
|
||||
|
||||
switch (widget.mode) {
|
||||
case MPinMode.enter:
|
||||
final storedPin = await storage.read('mpin');
|
||||
log('storedPin: $storedPin');
|
||||
if (storedPin == int.tryParse(pin)) {
|
||||
widget.onCompleted?.call(pin);
|
||||
} else {
|
||||
setState(() {
|
||||
errorText = "Incorrect mPIN. Try again.";
|
||||
mPin.clear();
|
||||
});
|
||||
}
|
||||
break;
|
||||
case MPinMode.set:
|
||||
// propagate parent onCompleted into confirm step
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => MPinScreen(
|
||||
mode: MPinMode.confirm,
|
||||
initialPin: pin,
|
||||
onCompleted: widget.onCompleted, // <-- use parent callback
|
||||
),
|
||||
),
|
||||
);
|
||||
break;
|
||||
case MPinMode.confirm:
|
||||
if (widget.initialPin == pin) {
|
||||
// 1) persist the pin
|
||||
await storage.write('mpin', pin);
|
||||
|
||||
// 3) now clear the entire navigation stack and go to your main scaffold
|
||||
if (mounted) {
|
||||
Navigator.of(context, rootNavigator: true).pushAndRemoveUntil(
|
||||
MaterialPageRoute(builder: (_) => const NavigationScaffold()),
|
||||
(route) => false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
errorText = "Pins do not match. Try again.";
|
||||
mPin.clear();
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildMPinDots() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -63,24 +164,16 @@ class MPinScreenState extends State<MPinScreen> {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
onTap: () {
|
||||
if (key == '<') {
|
||||
deleteDigit();
|
||||
} else if (key == 'Enter') {
|
||||
if (mPin.length == 4) {
|
||||
// String storedMpin = await SecureStorage().read("mpin");
|
||||
// if(!mounted) return;
|
||||
// if(storedMpin == mPin.join()) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const NavigationScaffold()),
|
||||
);
|
||||
// }
|
||||
_handleComplete();
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text("Please enter 4 digits")),
|
||||
);
|
||||
setState(() {
|
||||
errorText = "Please enter 4 digits";
|
||||
});
|
||||
}
|
||||
} else if (key.isNotEmpty) {
|
||||
addDigit(key);
|
||||
@@ -94,15 +187,15 @@ class MPinScreenState extends State<MPinScreen> {
|
||||
color: Colors.grey[200],
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: key == 'Enter' ? const Icon(Symbols.check) : Text(
|
||||
key == '<' ? '⌫' : key,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: key == 'Enter' ?
|
||||
FontWeight.normal : FontWeight.normal,
|
||||
color: key == 'Enter' ? Colors.blue : Colors.black,
|
||||
),
|
||||
),
|
||||
child: key == 'Enter'
|
||||
? const Icon(Icons.check)
|
||||
: Text(
|
||||
key == '<' ? '⌫' : key,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: key == 'Enter' ? Colors.blue : Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -112,34 +205,40 @@ class MPinScreenState extends State<MPinScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
String getTitle() {
|
||||
switch (widget.mode) {
|
||||
case MPinMode.enter:
|
||||
return "Enter your mPIN";
|
||||
case MPinMode.set:
|
||||
return "Set your new mPIN";
|
||||
case MPinMode.confirm:
|
||||
return "Confirm your mPIN";
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cubit = context.read<AuthCubit>();
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
const Spacer(),
|
||||
// Logo
|
||||
const FlutterLogo(size: 100),
|
||||
Image.asset('assets/images/logo.png', height: 100),
|
||||
const SizedBox(height: 20),
|
||||
const Text(
|
||||
"Enter your mPIN",
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
|
||||
Text(
|
||||
getTitle(),
|
||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
buildMPinDots(),
|
||||
if (errorText != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child:
|
||||
Text(errorText!, style: const TextStyle(color: Colors.red)),
|
||||
),
|
||||
const Spacer(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
TextButton(onPressed: () {
|
||||
cubit.authenticateBiometric();
|
||||
}, child: const Text("Try another way")),
|
||||
TextButton(onPressed: () {}, child: const Text("Register?")),
|
||||
],
|
||||
),
|
||||
buildNumberPad(),
|
||||
const Spacer(),
|
||||
],
|
||||
@@ -147,4 +246,4 @@ class MPinScreenState extends State<MPinScreen> {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user