After Login Button
This commit is contained in:
33
lib/app.dart
33
lib/app.dart
@@ -4,7 +4,8 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.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_cubit.dart';
|
||||||
import 'package:kmobile/features/auth/controllers/theme_mode_state.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:kmobile/security/secure_storage.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import './l10n/app_localizations.dart';
|
import './l10n/app_localizations.dart';
|
||||||
@@ -14,7 +15,6 @@ import 'config/routes.dart';
|
|||||||
import 'di/injection.dart';
|
import 'di/injection.dart';
|
||||||
import 'features/auth/controllers/auth_cubit.dart';
|
import 'features/auth/controllers/auth_cubit.dart';
|
||||||
import 'features/card/screens/card_management_screen.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/service/screens/service_screen.dart';
|
||||||
import 'features/dashboard/screens/dashboard_screen.dart';
|
import 'features/dashboard/screens/dashboard_screen.dart';
|
||||||
import 'features/auth/screens/mpin_screen.dart';
|
import 'features/auth/screens/mpin_screen.dart';
|
||||||
@@ -37,7 +37,6 @@ class KMobile extends StatefulWidget {
|
|||||||
|
|
||||||
class _KMobileState extends State<KMobile> with WidgetsBindingObserver {
|
class _KMobileState extends State<KMobile> with WidgetsBindingObserver {
|
||||||
Timer? _backgroundTimer;
|
Timer? _backgroundTimer;
|
||||||
bool showSplash = true;
|
|
||||||
Locale? _locale;
|
Locale? _locale;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -45,11 +44,6 @@ class _KMobileState extends State<KMobile> with WidgetsBindingObserver {
|
|||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
loadPreferences();
|
loadPreferences();
|
||||||
Future.delayed(const Duration(seconds: 3), () {
|
|
||||||
setState(() {
|
|
||||||
showSplash = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -132,8 +126,7 @@ class _KMobileState extends State<KMobile> with WidgetsBindingObserver {
|
|||||||
darkTheme: themeState.getDarkThemeData(),
|
darkTheme: themeState.getDarkThemeData(),
|
||||||
themeMode: context.watch<ThemeModeCubit>().state.mode,
|
themeMode: context.watch<ThemeModeCubit>().state.mode,
|
||||||
onGenerateRoute: AppRoutes.generateRoute,
|
onGenerateRoute: AppRoutes.generateRoute,
|
||||||
initialRoute: AppRoutes.splash,
|
home: const AuthGate(),
|
||||||
home: showSplash ? const SplashScreen() : const AuthGate(),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -205,7 +198,11 @@ class _AuthGateState extends State<AuthGate> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_checking) {
|
if (_checking) {
|
||||||
return const SplashScreen();
|
return const Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (_isLoggedIn) {
|
if (_isLoggedIn) {
|
||||||
if (_hasMPin) {
|
if (_hasMPin) {
|
||||||
@@ -214,7 +211,11 @@ class _AuthGateState extends State<AuthGate> {
|
|||||||
future: _tryBiometric(),
|
future: _tryBiometric(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
return const SplashScreen();
|
return const Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (snapshot.data == true) {
|
if (snapshot.data == true) {
|
||||||
return const NavigationScaffold();
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Future.microtask(() => _showDialog(context));
|
Future.microtask(() => _showDialog(context));
|
||||||
return const SplashScreen();
|
return const Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showDialog(BuildContext context) async {
|
Future<void> _showDialog(BuildContext context) async {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:kmobile/features/auth/screens/mpin_screen.dart';
|
import 'package:kmobile/features/auth/screens/mpin_screen.dart';
|
||||||
import 'package:kmobile/features/auth/screens/splash_screen.dart';
|
|
||||||
import '../app.dart';
|
import '../app.dart';
|
||||||
import '../features/auth/screens/login_screen.dart';
|
import '../features/auth/screens/login_screen.dart';
|
||||||
// import '../features/auth/screens/forgot_password_screen.dart';
|
// import '../features/auth/screens/forgot_password_screen.dart';
|
||||||
@@ -30,8 +29,6 @@ class AppRoutes {
|
|||||||
// Route generator
|
// Route generator
|
||||||
static Route<dynamic> generateRoute(RouteSettings settings) {
|
static Route<dynamic> generateRoute(RouteSettings settings) {
|
||||||
switch (settings.name) {
|
switch (settings.name) {
|
||||||
case splash:
|
|
||||||
return MaterialPageRoute(builder: (_) => const SplashScreen());
|
|
||||||
case login:
|
case login:
|
||||||
return MaterialPageRoute(builder: (_) => const LoginScreen());
|
return MaterialPageRoute(builder: (_) => const LoginScreen());
|
||||||
|
|
||||||
|
|||||||
@@ -69,9 +69,9 @@ Dio _createDioClient() {
|
|||||||
final dio = Dio(
|
final dio = Dio(
|
||||||
BaseOptions(
|
BaseOptions(
|
||||||
baseUrl:
|
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
|
//'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),
|
connectTimeout: const Duration(seconds: 60),
|
||||||
receiveTimeout: const Duration(seconds: 60),
|
receiveTimeout: const Duration(seconds: 60),
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ class AuthCubit extends Cubit<AuthState> {
|
|||||||
checkAuthStatus();
|
checkAuthStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
emit(AuthInitial());
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> checkAuthStatus() async {
|
Future<void> checkAuthStatus() async {
|
||||||
emit(AuthLoading());
|
emit(AuthLoading());
|
||||||
try {
|
try {
|
||||||
@@ -27,6 +31,10 @@ class AuthCubit extends Cubit<AuthState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void startVerification() {
|
||||||
|
emit(AuthVerificationInProgress());
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> refreshUserData() async {
|
Future<void> refreshUserData() async {
|
||||||
try {
|
try {
|
||||||
// emit(AuthLoading());
|
// emit(AuthLoading());
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ class AuthInitial extends AuthState {}
|
|||||||
|
|
||||||
class AuthLoading extends AuthState {}
|
class AuthLoading extends AuthState {}
|
||||||
|
|
||||||
|
class AuthVerificationInProgress extends AuthState {}
|
||||||
|
|
||||||
class Authenticated extends AuthState {
|
class Authenticated extends AuthState {
|
||||||
final List<User> users;
|
final List<User> users;
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
import '../../../l10n/app_localizations.dart';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:kmobile/di/injection.dart';
|
import 'package:kmobile/features/auth/controllers/auth_cubit.dart';
|
||||||
import 'package:kmobile/features/auth/screens/mpin_screen.dart';
|
import 'package:kmobile/features/auth/screens/verification_screen.dart';
|
||||||
import 'package:kmobile/features/auth/screens/set_password_screen.dart';
|
import 'package:kmobile/l10n/app_localizations.dart';
|
||||||
import 'package:kmobile/security/secure_storage.dart';
|
|
||||||
import '../../../app.dart';
|
|
||||||
import '../controllers/auth_cubit.dart';
|
|
||||||
import '../controllers/auth_state.dart';
|
|
||||||
|
|
||||||
class LoginScreen extends StatefulWidget {
|
class LoginScreen extends StatefulWidget {
|
||||||
const LoginScreen({super.key});
|
const LoginScreen({super.key});
|
||||||
@@ -23,7 +17,12 @@ class LoginScreenState extends State<LoginScreen>
|
|||||||
final _customerNumberController = TextEditingController();
|
final _customerNumberController = TextEditingController();
|
||||||
final _passwordController = TextEditingController();
|
final _passwordController = TextEditingController();
|
||||||
bool _obscurePassword = true;
|
bool _obscurePassword = true;
|
||||||
//bool _showWelcome = true;
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
context.read<AuthCubit>().reset();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -34,10 +33,14 @@ class LoginScreenState extends State<LoginScreen>
|
|||||||
|
|
||||||
void _submitForm() {
|
void _submitForm() {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
context.read<AuthCubit>().login(
|
Navigator.of(context).push(
|
||||||
_customerNumberController.text.trim(),
|
MaterialPageRoute(
|
||||||
_passwordController.text,
|
builder: (_) => VerificationScreen(
|
||||||
);
|
customerNo: _customerNumberController.text.trim(),
|
||||||
|
password: _passwordController.text,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,217 +48,141 @@ class LoginScreenState extends State<LoginScreen>
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
// appBar: AppBar(title: const Text('Login')),
|
// appBar: AppBar(title: const Text('Login')),
|
||||||
body: BlocConsumer<AuthCubit, AuthState>(
|
body: Padding(
|
||||||
listener: (context, state) async {
|
padding: const EdgeInsets.all(24.0),
|
||||||
if (state is Authenticated) {
|
child: Form(
|
||||||
final storage = getIt<SecureStorage>();
|
key: _formKey,
|
||||||
final mpin = await storage.read('mpin');
|
child: Column(
|
||||||
if (!context.mounted) return;
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
if (mpin == null) {
|
children: [
|
||||||
Navigator.of(context).pushReplacement(
|
Image.asset(
|
||||||
MaterialPageRoute(
|
'assets/images/logo.png',
|
||||||
builder: (_) => MPinScreen(
|
width: 150,
|
||||||
mode: MPinMode.set,
|
height: 150,
|
||||||
onCompleted: (_) {
|
errorBuilder: (context, error, stackTrace) {
|
||||||
Navigator.of(
|
return Icon(
|
||||||
context,
|
Icons.account_balance,
|
||||||
rootNavigator: true,
|
size: 100,
|
||||||
).pushReplacement(
|
color: Theme.of(context).primaryColor,
|
||||||
MaterialPageRoute(
|
);
|
||||||
builder: (_) => const NavigationScaffold(),
|
},
|
||||||
),
|
),
|
||||||
);
|
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) {
|
||||||
} else {
|
if (value == null || value.isEmpty) {
|
||||||
Navigator.of(context).pushReplacement(
|
return AppLocalizations.of(context).pleaseEnterPassword;
|
||||||
MaterialPageRoute(builder: (_) => const NavigationScaffold()),
|
}
|
||||||
);
|
return null;
|
||||||
}
|
},
|
||||||
} 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),),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
131
lib/features/auth/screens/sms_verification_helper.dart
Normal file
131
lib/features/auth/screens/sms_verification_helper.dart
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
146
lib/features/auth/screens/verification_screen.dart
Normal file
146
lib/features/auth/screens/verification_screen.dart
Normal 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"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user