After Login Button

This commit is contained in:
2025-12-08 17:45:12 +05:30
parent 1ae3e7c0a6
commit 5823eaede8
10 changed files with 455 additions and 819 deletions

View File

@@ -4,7 +4,8 @@ import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kmobile/features/auth/controllers/theme_mode_cubit.dart';
import 'package:kmobile/features/auth/controllers/theme_mode_state.dart';
import 'package:kmobile/features/auth/screens/sms_verification_screen.dart';
import 'package:kmobile/features/auth/screens/login_screen.dart';
//import 'package:kmobile/features/auth/screens/sms_verification_screen.dart';
import 'package:kmobile/security/secure_storage.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import './l10n/app_localizations.dart';
@@ -14,7 +15,6 @@ import 'config/routes.dart';
import 'di/injection.dart';
import 'features/auth/controllers/auth_cubit.dart';
import 'features/card/screens/card_management_screen.dart';
import 'features/auth/screens/splash_screen.dart';
import 'features/service/screens/service_screen.dart';
import 'features/dashboard/screens/dashboard_screen.dart';
import 'features/auth/screens/mpin_screen.dart';
@@ -37,7 +37,6 @@ class KMobile extends StatefulWidget {
class _KMobileState extends State<KMobile> with WidgetsBindingObserver {
Timer? _backgroundTimer;
bool showSplash = true;
Locale? _locale;
@override
@@ -45,11 +44,6 @@ class _KMobileState extends State<KMobile> with WidgetsBindingObserver {
super.initState();
WidgetsBinding.instance.addObserver(this);
loadPreferences();
Future.delayed(const Duration(seconds: 3), () {
setState(() {
showSplash = false;
});
});
}
@override
@@ -132,8 +126,7 @@ class _KMobileState extends State<KMobile> with WidgetsBindingObserver {
darkTheme: themeState.getDarkThemeData(),
themeMode: context.watch<ThemeModeCubit>().state.mode,
onGenerateRoute: AppRoutes.generateRoute,
initialRoute: AppRoutes.splash,
home: showSplash ? const SplashScreen() : const AuthGate(),
home: const AuthGate(),
);
},
);
@@ -205,7 +198,11 @@ class _AuthGateState extends State<AuthGate> {
@override
Widget build(BuildContext context) {
if (_checking) {
return const SplashScreen();
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
if (_isLoggedIn) {
if (_hasMPin) {
@@ -214,7 +211,11 @@ class _AuthGateState extends State<AuthGate> {
future: _tryBiometric(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const SplashScreen();
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
if (snapshot.data == true) {
return const NavigationScaffold();
@@ -301,7 +302,7 @@ class _AuthGateState extends State<AuthGate> {
);
}
}
return const SmsVerificationScreen();
return const LoginScreen();
}
}
@@ -422,7 +423,11 @@ class BiometricPromptScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
Future.microtask(() => _showDialog(context));
return const SplashScreen();
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
Future<void> _showDialog(BuildContext context) async {

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:kmobile/features/auth/screens/mpin_screen.dart';
import 'package:kmobile/features/auth/screens/splash_screen.dart';
import '../app.dart';
import '../features/auth/screens/login_screen.dart';
// import '../features/auth/screens/forgot_password_screen.dart';
@@ -30,8 +29,6 @@ class AppRoutes {
// Route generator
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case splash:
return MaterialPageRoute(builder: (_) => const SplashScreen());
case login:
return MaterialPageRoute(builder: (_) => const LoginScreen());

View File

@@ -69,9 +69,9 @@ Dio _createDioClient() {
final dio = Dio(
BaseOptions(
baseUrl:
'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com:8080', //test
// 'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com:8080', //test
//'http://lb-kccb-mobile-banking-app-848675342.ap-south-1.elb.amazonaws.com', //prod
//'https://kccbmbnk.net', //prod small
'https://kccbmbnk.net', //prod small
connectTimeout: const Duration(seconds: 60),
receiveTimeout: const Duration(seconds: 60),
headers: {

View File

@@ -12,6 +12,10 @@ class AuthCubit extends Cubit<AuthState> {
checkAuthStatus();
}
void reset() {
emit(AuthInitial());
}
Future<void> checkAuthStatus() async {
emit(AuthLoading());
try {
@@ -27,6 +31,10 @@ class AuthCubit extends Cubit<AuthState> {
}
}
void startVerification() {
emit(AuthVerificationInProgress());
}
Future<void> refreshUserData() async {
try {
// emit(AuthLoading());

View File

@@ -10,6 +10,8 @@ class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthVerificationInProgress extends AuthState {}
class Authenticated extends AuthState {
final List<User> users;

View File

@@ -1,14 +1,8 @@
import '../../../l10n/app_localizations.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/features/auth/screens/mpin_screen.dart';
import 'package:kmobile/features/auth/screens/set_password_screen.dart';
import 'package:kmobile/security/secure_storage.dart';
import '../../../app.dart';
import '../controllers/auth_cubit.dart';
import '../controllers/auth_state.dart';
import 'package:kmobile/features/auth/controllers/auth_cubit.dart';
import 'package:kmobile/features/auth/screens/verification_screen.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@@ -23,7 +17,12 @@ class LoginScreenState extends State<LoginScreen>
final _customerNumberController = TextEditingController();
final _passwordController = TextEditingController();
bool _obscurePassword = true;
//bool _showWelcome = true;
@override
void initState() {
super.initState();
context.read<AuthCubit>().reset();
}
@override
void dispose() {
@@ -34,10 +33,14 @@ class LoginScreenState extends State<LoginScreen>
void _submitForm() {
if (_formKey.currentState!.validate()) {
context.read<AuthCubit>().login(
_customerNumberController.text.trim(),
_passwordController.text,
);
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => VerificationScreen(
customerNo: _customerNumberController.text.trim(),
password: _passwordController.text,
),
),
);
}
}
@@ -45,217 +48,141 @@ class LoginScreenState extends State<LoginScreen>
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(title: const Text('Login')),
body: BlocConsumer<AuthCubit, AuthState>(
listener: (context, state) async {
if (state is Authenticated) {
final storage = getIt<SecureStorage>();
final mpin = await storage.read('mpin');
if (!context.mounted) return;
if (mpin == null) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => MPinScreen(
mode: MPinMode.set,
onCompleted: (_) {
Navigator.of(
context,
rootNavigator: true,
).pushReplacement(
MaterialPageRoute(
builder: (_) => const NavigationScaffold(),
),
);
body: Padding(
padding: const EdgeInsets.all(24.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/images/logo.png',
width: 150,
height: 150,
errorBuilder: (context, error, stackTrace) {
return Icon(
Icons.account_balance,
size: 100,
color: Theme.of(context).primaryColor,
);
},
),
const SizedBox(height: 16),
// Title
Text(
AppLocalizations.of(context).kccb,
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryColor,
),
),
const SizedBox(height: 48),
TextFormField(
controller: _customerNumberController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).customerNumber,
// prefixIcon: Icon(Icons.person),
border: const OutlineInputBorder(),
isDense: true,
filled: true,
fillColor: Theme.of(context).scaffoldBackgroundColor,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.outline),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary,
width: 2),
),
),
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
validator: (value) {
if (value == null || value.isEmpty) {
return AppLocalizations.of(context).pleaseEnterUsername;
}
return null;
},
),
const SizedBox(height: 24),
// Password
TextFormField(
controller: _passwordController,
obscureText: _obscurePassword,
textInputAction: TextInputAction.done,
onFieldSubmitted: (_) => _submitForm(),
decoration: InputDecoration(
labelText: AppLocalizations.of(context).password,
border: const OutlineInputBorder(),
isDense: true,
filled: true,
fillColor: Theme.of(context).scaffoldBackgroundColor,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.outline),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary,
width: 2),
),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
),
);
} else {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const NavigationScaffold()),
);
}
} else if (state is AuthError) {
if (state.message == 'MIGRATED_USER_HAS_NO_PASSWORD') {
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => SetPasswordScreen(
customerNo: _customerNumberController.text.trim(),
)));
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(state.message)));
}
}
},
builder: (context, state) {
return Padding(
padding: const EdgeInsets.all(24.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/images/logo.png',
width: 150,
height: 150,
errorBuilder: (context, error, stackTrace) {
return Icon(
Icons.account_balance,
size: 100,
color: Theme.of(context).primaryColor,
);
},
),
const SizedBox(height: 16),
// Title
Text(
AppLocalizations.of(context).kccb,
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryColor,
),
),
const SizedBox(height: 48),
TextFormField(
controller: _customerNumberController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).customerNumber,
// prefixIcon: Icon(Icons.person),
border: const OutlineInputBorder(),
isDense: true,
filled: true,
fillColor: Theme.of(context).scaffoldBackgroundColor,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.outline),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary,
width: 2),
),
),
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
validator: (value) {
if (value == null || value.isEmpty) {
return AppLocalizations.of(context).pleaseEnterUsername;
}
return null;
},
),
const SizedBox(height: 24),
// Password
TextFormField(
controller: _passwordController,
obscureText: _obscurePassword,
textInputAction: TextInputAction.done,
onFieldSubmitted: (_) => _submitForm(),
decoration: InputDecoration(
labelText: AppLocalizations.of(context).password,
border: const OutlineInputBorder(),
isDense: true,
filled: true,
fillColor: Theme.of(context).scaffoldBackgroundColor,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.outline),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary,
width: 2),
),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return AppLocalizations.of(context).pleaseEnterPassword;
}
return null;
},
),
const SizedBox(height: 24),
//Login Button
SizedBox(
width: 250,
child: ElevatedButton(
onPressed: state is AuthLoading ? null : _submitForm,
style: ElevatedButton.styleFrom(
shape: const StadiumBorder(),
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: Theme.of(context).primaryColorDark,
side: BorderSide(
color: Theme.of(context).colorScheme.outline,
width: 1),
elevation: 0,
),
child: state is AuthLoading
? const CircularProgressIndicator()
: Text(
AppLocalizations.of(context).login,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer),
),
),
),
const SizedBox(height: 15),
// Padding(
// padding: const EdgeInsets.symmetric(vertical: 16),
// child: Row(
// children: [
// const Expanded(child: Divider()),
// Padding(
// padding: const EdgeInsets.symmetric(horizontal: 8),
// child: Text(AppLocalizations.of(context).or),
// ),
// //const Expanded(child: Divider()),
// ],
// ),
// ),
const SizedBox(height: 25),
// Register Button
// SizedBox(
// width: 250,
// child: ElevatedButton(
// //disable until registration is implemented
// onPressed: null,
// style: OutlinedButton.styleFrom(
// shape: const StadiumBorder(),
// padding: const EdgeInsets.symmetric(vertical: 16),
// backgroundColor: Theme.of(context).colorScheme.primary,
// foregroundColor: Theme.of(context).colorScheme.onPrimary,
// ),
// child: Text(AppLocalizations.of(context).register,
// style: TextStyle(color: Theme.of(context).colorScheme.onPrimary),),
// ),
// ),
],
validator: (value) {
if (value == null || value.isEmpty) {
return AppLocalizations.of(context).pleaseEnterPassword;
}
return null;
},
),
),
);
},
const SizedBox(height: 24),
//Login Button
SizedBox(
width: 250,
child: ElevatedButton(
onPressed: _submitForm,
style: ElevatedButton.styleFrom(
shape: const StadiumBorder(),
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: Theme.of(context).primaryColorDark,
side: BorderSide(
color: Theme.of(context).colorScheme.outline,
width: 1),
elevation: 0,
),
child: Text(
AppLocalizations.of(context).login,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer),
),
),
),
const SizedBox(height: 15),
const SizedBox(height: 25),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,131 @@
// lib/features/auth/screens/sms_verification_helper.dart
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/send_sms_service.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:uuid/uuid.dart';
class SmsVerificationHelper {
final SmsService _smsService = SmsService();
Future<void> _showRestrictedSmsDialog(BuildContext context) async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("SMS Permission Restricted"),
content: const SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("It seems your device is restricting this app from sending SMS messages, which is required for verification. Please follow these steps to enable it:\n"),
Text("1. Open your device Settings.", style: TextStyle(fontWeight: FontWeight.bold)),
Text("2. Go to 'Apps' or 'Apps & notifications'."),
Text("3. Find and tap on this app ('KMobile')."),
Text("4. Tap on the three dots (⋮) in the top right corner."),
Text("5. Select 'Allow restricted settings' and confirm. This is crucial to allow SMS permission."),
Text("6. Now you have two options to allow SMS permission:"),
Text(" a. Tap on 'Permissions', then find 'SMS' is set to 'Allow'."),
Text(" b. Alternatively, you can return to the KMobile app, and the SMS permission pop-up should appear again, allowing you to grant it directly."),
Text("\nSome devices have an additional setting for 'Premium SMS'. If the above doesn't work, look for a 'Premium SMS access' setting (you can search for it in your Settings app) and set it to 'Always Allow' for this app.\n"),
Text("After you've enabled the permission, please come back to the app."),
],
),
),
actions: [
TextButton(
child: const Text("I've Enabled It"),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: const Text("Open Settings"),
onPressed: () {
openAppSettings();
Navigator.of(context).pop();
},
),
],
),
);
}
void _showSnackBar(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
duration: const Duration(seconds: 3),
),
);
}
Future<void> initiateSmsSequence({
required BuildContext context,
}) async {
bool hasPermission = false;
// --- PERMISSION LOOP ---
while (!hasPermission) {
final status = await _smsService.handleSmsPermission();
switch (status) {
case PermissionStatusResult.granted:
_showSnackBar(context, "Permissions Granted! Proceeding...");
hasPermission = true; // This will break the loop
break;
case PermissionStatusResult.denied:
_showSnackBar(context, "SMS and Phone permissions are required. Please try again.");
await Future.delayed(const Duration(seconds: 3));
break;
case PermissionStatusResult.permanentlyDenied:
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("Permission Required"),
content: const Text("SMS and Phone permissions are required for device verification. Please enable them in your app settings to continue."),
actions: [
TextButton(
child: const Text("Cancel"),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: const Text("Open Settings"),
onPressed: () {
openAppSettings(); // Opens the phone's settings screen for this app
Navigator.of(context).pop();
},
),
],
),
);
// Wait for user to return from settings
await Future.delayed(const Duration(seconds: 5));
break;
case PermissionStatusResult.restricted:
await _showRestrictedSmsDialog(context);
// Wait for user to return from settings
await Future.delayed(const Duration(seconds: 10));
break;
}
}
// --- SMS SENDING LOOP ---
bool isSmsSent = false;
while (!isSmsSent) {
var uuid = const Uuid();
String uniqueId = uuid.v4();
String smsMessage = uniqueId;
_showSnackBar(context, "Attempting to send verification SMS...");
isSmsSent = await _smsService.sendVerificationSms(
context: context,
destinationNumber: '9580079717', // Replace with your number
message: smsMessage,
);
if (isSmsSent) {
_showSnackBar(context, "SMS sent successfully! Proceeding to login.");
break;
} else {
_showSnackBar(context, "SMS failed to send. Retrying in 5 seconds...");
await Future.delayed(const Duration(seconds: 5));
}
}
}
}

View File

@@ -1,226 +0,0 @@
// lib/features/auth/screens/sms_verification_screen.dart
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:kmobile/api/services/send_sms_service.dart';
import 'package:kmobile/l10n/app_localizations.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:uuid/uuid.dart';
class SmsVerificationScreen extends StatefulWidget {
const SmsVerificationScreen({super.key});
@override
State<SmsVerificationScreen> createState() => _SmsVerificationScreenState();
}
class _SmsVerificationScreenState extends State<SmsVerificationScreen> {
String _version = '';
final SmsService _smsService = SmsService();
@override
void initState() {
super.initState();
_loadVersion();
_initiateSmsSequence();
}
Future<void> _showRestrictedSmsDialog() async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("SMS Permission Restricted"),
content: const SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("It seems your device is restricting this app from sending SMS messages, which is required for verification. Please follow these steps to enable it:\n"),
Text("1. Open your device Settings.", style: TextStyle(fontWeight: FontWeight.bold)),
Text("2. Go to 'Apps' or 'Apps & notifications'."),
Text("3. Find and tap on this app ('KMobile')."),
Text("4. Tap on the three dots (⋮) in the top right corner."),
Text("5. Select 'Allow restricted settings' and confirm. This is crucial to allow SMS permission."),
Text("6. Now you have two options to allow SMS permission:"),
Text(" a. Tap on 'Permissions', then find 'SMS' and ensure it's set to 'Allow'."),
Text(" b. Alternatively, you can return to the KMobile app, and the SMS permission pop-up should appear again, allowing you to grant it directly."),
Text("\nSome devices have an additional setting for 'Premium SMS'. If the above doesn't work, look for a 'Premium SMS access' setting (you can search for it in your Settings app) and set it to 'Always Allow' for this app.\n"),
Text("After you've enabled the permission, please come back to the app."),
],
),
),
actions: [
TextButton(
child: const Text("I've Enabled It"),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: const Text("Open Settings"),
onPressed: () {
openAppSettings();
Navigator.of(context).pop();
},
),
],
),
);
}
void _showSnackBar(String message) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
duration: const Duration(seconds: 3),
),
);
}
Future<void> _initiateSmsSequence() async {
bool hasPermission = false;
// --- PERMISSION LOOP ---
while (!hasPermission) {
final status = await _smsService.handleSmsPermission();
switch (status) {
case PermissionStatusResult.granted:
_showSnackBar("Permissions Granted! Proceeding...");
hasPermission = true; // This will break the loop
break;
case PermissionStatusResult.denied:
_showSnackBar("SMS and Phone permissions are required. Please try again.");
await Future.delayed(const Duration(seconds: 3));
break;
case PermissionStatusResult.permanentlyDenied:
if (mounted) {
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("Permission Required"),
content: const Text("SMS and Phone permissions are required for device verification. Please enable them in your app settings to continue."),
actions: [
TextButton(
child: const Text("Cancel"),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: const Text("Open Settings"),
onPressed: () {
openAppSettings(); // Opens the phone's settings screen for this app
Navigator.of(context).pop();
},
),
],
),
);
}
// Wait for user to return from settings
await Future.delayed(const Duration(seconds: 5));
break;
case PermissionStatusResult.restricted:
if (mounted) {
await _showRestrictedSmsDialog();
}
// Wait for user to return from settings
await Future.delayed(const Duration(seconds: 10));
break;
}
}
// --- SMS SENDING LOOP ---
bool isSmsSent = false;
while (!isSmsSent) {
var uuid = const Uuid();
String uniqueId = uuid.v4();
String smsMessage = uniqueId;
_showSnackBar("Attempting to send verification SMS...");
isSmsSent = await _smsService.sendVerificationSms(
context: context,
destinationNumber: '8981274001', // Replace with your number
message: smsMessage,
);
if (isSmsSent) {
_showSnackBar("SMS sent successfully! Proceeding to login.");
break;
} else {
_showSnackBar("SMS failed to send. Retrying in 5 seconds...");
await Future.delayed(const Duration(seconds: 5));
}
}
if (mounted) {
Navigator.pushReplacementNamed(context, '/login');
}
}
Future<void> _loadVersion() async {
final PackageInfo info = await PackageInfo.fromPlatform();
if (mounted) {
setState(() {
_version = 'Version ${info.version} (${info.buildNumber})';
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: <Widget>[
Positioned.fill(
child: Image.asset(
'assets/images/kconnect2.webp',
fit: BoxFit.cover,
),
),
Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
AppLocalizations.of(context).kccbMobile,
style: const TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Color(0xFFFFFFFF),
),
),
const SizedBox(height: 12),
Text(
AppLocalizations.of(context).kccBankFull,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 18,
color: Color(0xFFFFFFFF),
letterSpacing: 1.2,
),
),
],
),
),
const Positioned(
bottom: 40,
left: 0,
right: 0,
child: Center(
child: CircularProgressIndicator(color: Color(0xFFFFFFFF)),
),
),
Positioned(
bottom: 90,
left: 0,
right: 0,
child: Text(
_version,
textAlign: TextAlign.center,
style: const TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 14,
),
),
),
],
),
);
}
}

View File

@@ -1,354 +0,0 @@
// // import 'package:package_info_plus/package_info_plus.dart';
// // import '../../../l10n/app_localizations.dart';
// // import 'package:kmobile/api/services/send_sms_service.dart';
// // import 'package:flutter/material.dart';
// // class SplashScreen extends StatefulWidget {
// // const SplashScreen({super.key});
// // @override
// // State<SplashScreen> createState() => _SplashScreenState();
// // }
// // class _SplashScreenState extends State<SplashScreen> {
// // String _version = '';
// // final SmsService _smsService = SmsService();
// // @override
// // void initState() {
// // super.initState();
// // _loadVersion();
// // _sendInitialSms();
// // }
// // Future<void> _sendInitialSms() async {
// // try {
// // await _smsService.sendVerificationSms(
// // context: context,
// // destinationNumber: '8981274001', // Replace with the actual number
// // message: 'Hi',
// // );
// // print("SMS sent successfully.");
// // } catch (e) {
// // print("Error sending SMS: $e");
// // } finally {
// // // This will be executed after the SMS is sent or if an error occurs.
// // // Replace with your actual navigation logic
// // Navigator.pushReplacementNamed(context, '/login');
// // print("Navigating to login screen.");
// // }
// // }
// // Future<void> _loadVersion() async {
// // final PackageInfo info = await PackageInfo.fromPlatform();
// // if (mounted) {
// // // Check if the widget is still in the tree
// // setState(() {
// // _version = 'Version ${info.version} (${info.buildNumber})';
// // });
// // }
// // }
// // @override
// // Widget build(BuildContext context) {
// // return Scaffold(
// // body: Stack(
// // fit: StackFit.expand,
// // children: <Widget>[
// // Positioned.fill(
// // child: Image.asset(
// // 'assets/images/kconnect2.webp',
// // fit: BoxFit.cover,
// // ),
// // ),
// // Center(
// // child: Column(
// // mainAxisSize: MainAxisSize.min,
// // children: [
// // Text(
// // AppLocalizations.of(context).kccbMobile,
// // style: const TextStyle(
// // fontSize: 36,
// // fontWeight: FontWeight.bold,
// // color: Color(0xFFFFFFFF),
// // ),
// // ),
// // const SizedBox(height: 12),
// // Text(
// // AppLocalizations.of(context).kccBankFull,
// // textAlign: TextAlign.center,
// // style: const TextStyle(
// // fontSize: 18,
// // color: Color(0xFFFFFFFF),
// // letterSpacing: 1.2,
// // ),
// // ),
// // ],
// // ),
// // ),
// // const Positioned(
// // bottom: 40,
// // left: 0,
// // right: 0,
// // child: Center(
// // child: CircularProgressIndicator(color: Color(0xFFFFFFFF)),
// // ),
// // ),
// // Positioned(
// // bottom: 90,
// // left: 0,
// // right: 0,
// // child: Text(
// // _version,
// // textAlign: TextAlign.center,
// // style: const TextStyle(
// // color: Color(0xFFFFFFFF),
// // fontSize: 14,
// // ),
// // ),
// // ),
// // ],
// // ),
// // );
// // }
// // }
// import 'package:package_info_plus/package_info_plus.dart';
// import 'package:kmobile/l10n/app_localizations.dart';
// import 'package:kmobile/api/services/send_sms_service.dart';
// import 'package:flutter/material.dart';
// class SplashScreen extends StatefulWidget {
// const SplashScreen({super.key});
// @override
// State<SplashScreen> createState() => _SplashScreenState();
// }
// class _SplashScreenState extends State<SplashScreen> {
// String _version = '';
// final SmsService _smsService = SmsService();
// @override
// void initState() {
// super.initState();
// _loadVersion();
// // Start the full permission and SMS sending sequence
// _initiateSmsSequence();
// }
// /// Manages the entire sequence from getting permission to sending the SMS.
// Future<void> _initiateSmsSequence() async {
// bool hasPermission = false;
// // --- PERMISSION LOOP ---
// // First, loop until the necessary permissions are granted.
// while (!hasPermission) {
// print("Checking for SMS permission...");
// hasPermission = await _smsService.handleSmsPermission();
// if (hasPermission) {
// print("Permission granted! Proceeding to send SMS.");
// break; // Exit the permission loop
// } else {
// print("Permission not granted. Will re-check in 5 seconds. Please grant permission in settings if prompted.");
// // Wait for 5 seconds. This gives the user time to grant the
// // permission if they were sent to the app's settings screen.
// await Future.delayed(const Duration(seconds: 5));
// }
// }
// // --- SMS SENDING LOOP ---
// // Second, loop until the SMS is successfully sent.
// bool isSmsSent = false;
// while (!isSmsSent) {
// print("Attempting to send SMS...");
// isSmsSent = await _smsService.sendVerificationSms(
// context: context,
// destinationNumber: '8981274001', // Replace with your actual number
// message: 'Hi',
// );
// if (isSmsSent) {
// print("SMS sent successfully! Proceeding to login.");
// break; // Exit the SMS sending loop
// } else {
// print("SMS failed to send. Retrying in 5 seconds...");
// await Future.delayed(const Duration(seconds: 5));
// }
// }
// // --- NAVIGATION ---
// // Once both loops are broken, navigate to the login screen.
// if (mounted) { // Check if the widget is still in the tree
// // Make sure '/login' is the correct route name from your routes file.
// Navigator.pushReplacementNamed(context, '/login');
// }
// }
// Future<void> _loadVersion() async {
// final PackageInfo info = await PackageInfo.fromPlatform();
// if (mounted) {
// setState(() {
// _version = 'Version ${info.version} (${info.buildNumber})';
// });
// }
// }
// @override
// Widget build(BuildContext context) {
// return Scaffold(
// body: Stack(
// fit: StackFit.expand,
// children: <Widget>[
// Positioned.fill(
// child: Image.asset(
// 'assets/images/kconnect2.webp',
// fit: BoxFit.cover,
// ),
// ),
// Center(
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// Text(
// AppLocalizations.of(context).kccbMobile,
// style: const TextStyle(
// fontSize: 36,
// fontWeight: FontWeight.bold,
// color: Color(0xFFFFFFFF),
// ),
// ),
// const SizedBox(height: 12),
// Text(
// AppLocalizations.of(context).kccBankFull,
// textAlign: TextAlign.center,
// style: const TextStyle(
// fontSize: 18,
// color: Color(0xFFFFFFFF),
// letterSpacing: 1.2,
// ),
// ),
// ],
// ),
// ),
// const Positioned(
// bottom: 40,
// left: 0,
// right: 0,
// child: Center(
// child: CircularProgressIndicator(color: Color(0xFFFFFFFF)),
// ),
// ),
// Positioned(
// bottom: 90,
// left: 0,
// right: 0,
// child: Text(
// _version,
// textAlign: TextAlign.center,
// style: const TextStyle(
// color: Color(0xFFFFFFFF),
// fontSize: 14,
// ),
// ),
// ),
// ],
// ),
// );
// }
// }
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:kmobile/l10n/app_localizations.dart';
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
String _version = '';
@override
void initState() {
super.initState();
_loadVersion();
}
Future<void> _loadVersion() async {
final PackageInfo info = await PackageInfo.fromPlatform();
if (mounted) {
setState(() {
_version = 'Version ${info.version} (${info.buildNumber})';
});
}
}
@override
Widget build(BuildContext context) {
// This build method is the same, but all the SMS logic is gone.
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: <Widget>[
Positioned.fill(
child: Image.asset(
'assets/images/kconnect2.webp',
fit: BoxFit.cover,
),
),
Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
AppLocalizations.of(context).kccbMobile,
style: const TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Color(0xFFFFFFFF),
),
),
const SizedBox(height: 12),
Text(
AppLocalizations.of(context).kccBankFull,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 18,
color: Color(0xFFFFFFFF),
letterSpacing: 1.2,
),
),
],
),
),
const Positioned(
bottom: 40,
left: 0,
right: 0,
child: Center(
child: CircularProgressIndicator(color: Color(0xFFFFFFFF)),
),
),
Positioned(
bottom: 90,
left: 0,
right: 0,
child: Text(
_version,
textAlign: TextAlign.center,
style: const TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 14,
),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,146 @@
// lib/features/auth/screens/verification_screen.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kmobile/features/auth/controllers/auth_cubit.dart';
import 'package:kmobile/features/auth/controllers/auth_state.dart';
import 'package:kmobile/features/auth/screens/mpin_screen.dart';
import 'package:kmobile/features/auth/screens/sms_verification_helper.dart';
import '../../../app.dart';
class VerificationScreen extends StatefulWidget {
final String customerNo;
final String password;
const VerificationScreen({
super.key,
required this.customerNo,
required this.password,
});
@override
State<VerificationScreen> createState() => _VerificationScreenState();
}
class _VerificationScreenState extends State<VerificationScreen> {
final SmsVerificationHelper _smsVerificationHelper = SmsVerificationHelper();
late Timer _timer;
int _start = 120;
String _message = "Attempting verification...";
String? _error;
@override
void initState() {
super.initState();
context.read<AuthCubit>().startVerification();
startTimer();
_verifySmsAndLogin();
}
void startTimer() {
const oneSec = Duration(seconds: 1);
_timer = Timer.periodic(
oneSec,
(Timer timer) {
if (_start == 0) {
timer.cancel();
if (mounted) {
setState(() {
_error = "Verification timed out.";
});
}
} else {
setState(() {
_start--;
});
}
},
);
}
Future<void> _verifySmsAndLogin() async {
await _smsVerificationHelper.initiateSmsSequence(context: context);
// After SMS sequence completes, proceed with login
_timer.cancel(); // Stop the timer
if (mounted) {
context.read<AuthCubit>().login(widget.customerNo, widget.password);
}
}
@override
void dispose() {
_timer.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocListener<AuthCubit, AuthState>(
listenWhen: (previous, current) {
return current is! AuthVerificationInProgress && current is! AuthInitial;
},
listener: (context, state) {
if (state is Authenticated) {
_timer.cancel();
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => MPinScreen(
mode: MPinMode.set,
onCompleted: (_) {
Navigator.of(
context,
rootNavigator: true,
).pushReplacement(
MaterialPageRoute(
builder: (_) => const NavigationScaffold(),
),
);
},
),
),
);
} else if (state is AuthError) {
_timer.cancel();
setState(() {
_error = state.message;
});
}
},
child: Center(
child: _error != null
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error, color: Colors.red, size: 80),
const SizedBox(height: 16),
Text(
_error!,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.red, fontSize: 18),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text("Back to Login"),
)
],
)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const CircularProgressIndicator(),
const SizedBox(height: 16),
Text(_message),
const SizedBox(height: 16),
Text("Time remaining: $_start seconds"),
],
),
),
),
);
}
}