Language Changes
This commit is contained in:
474
lib/features/auth/screens/login_screen.dart
Normal file
474
lib/features/auth/screens/login_screen.dart
Normal file
@@ -0,0 +1,474 @@
|
||||
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/security/secure_storage.dart';
|
||||
import '../../../app.dart';
|
||||
import '../controllers/auth_cubit.dart';
|
||||
import '../controllers/auth_state.dart';
|
||||
|
||||
class LoginScreen extends StatefulWidget {
|
||||
const LoginScreen({super.key});
|
||||
|
||||
@override
|
||||
LoginScreenState createState() => LoginScreenState();
|
||||
}
|
||||
|
||||
class LoginScreenState extends State<LoginScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _customerNumberController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
bool _obscurePassword = true;
|
||||
//bool _showWelcome = true;
|
||||
late AnimationController _logoController;
|
||||
late Animation<double> _logoAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_logoController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(seconds: 1),
|
||||
)..repeat(reverse: true);
|
||||
|
||||
_logoAnimation = Tween<double>(begin: 0.2, end: 1).animate(_logoController);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_logoController.dispose();
|
||||
_customerNumberController.dispose();
|
||||
_passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _submitForm() {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
context.read<AuthCubit>().login(
|
||||
_customerNumberController.text.trim(),
|
||||
_passwordController.text,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
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 (mpin == null) {
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => MPinScreen(
|
||||
mode: MPinMode.set,
|
||||
onCompleted: (_) {
|
||||
Navigator.of(context, rootNavigator: true)
|
||||
.pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const NavigationScaffold()),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => const NavigationScaffold()),
|
||||
);
|
||||
}
|
||||
} else if (state is AuthError) {
|
||||
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: [
|
||||
// 🔁 Animated Blinking Logo
|
||||
FadeTransition(
|
||||
opacity: _logoAnimation,
|
||||
child: Image.asset(
|
||||
'assets/images/logo.png',
|
||||
width: 150,
|
||||
height: 150,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return const Icon(Icons.account_balance,
|
||||
size: 100, color: Colors.blue);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Title
|
||||
const Text(
|
||||
'KCCB',
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blue),
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
|
||||
TextFormField(
|
||||
controller: _customerNumberController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Customer Number',
|
||||
// prefixIcon: Icon(Icons.person),
|
||||
border: OutlineInputBorder(),
|
||||
isDense: true,
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black, width: 2),
|
||||
),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
textInputAction: TextInputAction.next,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter your username';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
// Password
|
||||
TextFormField(
|
||||
controller: _passwordController,
|
||||
obscureText: _obscurePassword,
|
||||
textInputAction: TextInputAction.done,
|
||||
onFieldSubmitted: (_) =>
|
||||
_submitForm(), // ⌨️ Enter key submits
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Password',
|
||||
border: const OutlineInputBorder(),
|
||||
isDense: true,
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black, width: 2),
|
||||
),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
_obscurePassword
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_obscurePassword = !_obscurePassword;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter your password';
|
||||
}
|
||||
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: Colors.white,
|
||||
foregroundColor: Colors.blueAccent,
|
||||
side: const BorderSide(color: Colors.black, width: 1),
|
||||
elevation: 0,
|
||||
),
|
||||
child: state is AuthLoading
|
||||
? const CircularProgressIndicator()
|
||||
: const Text(
|
||||
"Login",
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: Divider()),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text('OR'),
|
||||
),
|
||||
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: Colors.lightBlue[100],
|
||||
foregroundColor: Colors.black),
|
||||
child: const Text('Register'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*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/security/secure_storage.dart';
|
||||
import '../../../app.dart';
|
||||
import '../controllers/auth_cubit.dart';
|
||||
import '../controllers/auth_state.dart';
|
||||
|
||||
class LoginScreen extends StatefulWidget {
|
||||
const LoginScreen({super.key});
|
||||
|
||||
@override
|
||||
LoginScreenState createState() => LoginScreenState();
|
||||
}
|
||||
|
||||
class LoginScreenState extends State<LoginScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _customerNumberController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
bool _obscurePassword = true;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_customerNumberController.dispose();
|
||||
_passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _submitForm() {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
context.read<AuthCubit>().login(
|
||||
_customerNumberController.text.trim(),
|
||||
_passwordController.text,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
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 (mpin == null) {
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => MPinScreen(
|
||||
mode: MPinMode.set,
|
||||
onCompleted: (_) {
|
||||
Navigator.of(context, rootNavigator: true)
|
||||
.pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const NavigationScaffold()),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => const NavigationScaffold()),
|
||||
);
|
||||
}
|
||||
} else if (state is AuthError) {
|
||||
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 const Icon(Icons.account_balance,
|
||||
size: 100, color: Colors.blue);
|
||||
}),
|
||||
const SizedBox(height: 16),
|
||||
// Title
|
||||
const Text(
|
||||
'KCCB',
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blue),
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
|
||||
TextFormField(
|
||||
controller: _customerNumberController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Customer Number',
|
||||
// prefixIcon: Icon(Icons.person),
|
||||
border: OutlineInputBorder(),
|
||||
isDense: true,
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black, width: 2),
|
||||
),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
textInputAction: TextInputAction.next,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter your username';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
TextFormField(
|
||||
controller: _passwordController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Password',
|
||||
// prefixIcon: const Icon(Icons.lock),
|
||||
border: const OutlineInputBorder(),
|
||||
isDense: true,
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black, width: 2),
|
||||
),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
_obscurePassword
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_obscurePassword = !_obscurePassword;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
textInputAction: TextInputAction.done,
|
||||
obscureText: _obscurePassword,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter your password';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
SizedBox(
|
||||
width: 250,
|
||||
child: ElevatedButton(
|
||||
onPressed: state is AuthLoading ? null : _submitForm,
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: const StadiumBorder(),
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.blueAccent,
|
||||
side: const BorderSide(color: Colors.black, width: 1),
|
||||
elevation: 0),
|
||||
child: state is AuthLoading
|
||||
? const CircularProgressIndicator()
|
||||
: const Text(
|
||||
'Login',
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: Divider()),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text('OR'),
|
||||
),
|
||||
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: Colors.lightBlue[100],
|
||||
foregroundColor: Colors.black),
|
||||
child: const Text('Register'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
*/
|
249
lib/features/auth/screens/mpin_screen.dart
Normal file
249
lib/features/auth/screens/mpin_screen.dart
Normal file
@@ -0,0 +1,249 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.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 {
|
||||
final MPinMode mode;
|
||||
final String? initialPin;
|
||||
final void Function(String pin)? onCompleted;
|
||||
|
||||
const MPinScreen({
|
||||
super.key,
|
||||
required this.mode,
|
||||
this.initialPin,
|
||||
this.onCompleted,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MPinScreen> createState() => _MPinScreenState();
|
||||
}
|
||||
|
||||
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 != null && 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void deleteDigit() {
|
||||
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,
|
||||
children: List.generate(4, (index) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(8),
|
||||
width: 15,
|
||||
height: 15,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: index < mPin.length ? Colors.black : Colors.grey[400],
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildNumberPad() {
|
||||
List<List<String>> keys = [
|
||||
['1', '2', '3'],
|
||||
['4', '5', '6'],
|
||||
['7', '8', '9'],
|
||||
['Enter', '0', '<']
|
||||
];
|
||||
|
||||
return Column(
|
||||
children: keys.map((row) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: row.map((key) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (key == '<') {
|
||||
deleteDigit();
|
||||
} else if (key == 'Enter') {
|
||||
if (mPin.length == 4) {
|
||||
_handleComplete();
|
||||
} else {
|
||||
setState(() {
|
||||
errorText = "Please enter 4 digits";
|
||||
});
|
||||
}
|
||||
} else if (key.isNotEmpty) {
|
||||
addDigit(key);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: 70,
|
||||
height: 70,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.grey[200],
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: key == 'Enter'
|
||||
? const Icon(Icons.check)
|
||||
: Text(
|
||||
key == '<' ? '⌫' : key,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: key == 'Enter' ? Colors.blue : Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
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) {
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
const Spacer(),
|
||||
// Logo
|
||||
Image.asset('assets/images/logo.png', height: 100),
|
||||
const SizedBox(height: 20),
|
||||
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(),
|
||||
buildNumberPad(),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
141
lib/features/auth/screens/welcome_screen.dart
Normal file
141
lib/features/auth/screens/welcome_screen.dart
Normal file
@@ -0,0 +1,141 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:async';
|
||||
|
||||
class WelcomeScreen extends StatefulWidget {
|
||||
final VoidCallback onContinue;
|
||||
|
||||
const WelcomeScreen({super.key, required this.onContinue});
|
||||
|
||||
@override
|
||||
State<WelcomeScreen> createState() => _WelcomeScreenState();
|
||||
}
|
||||
|
||||
class _WelcomeScreenState extends State<WelcomeScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Automatically go to login after 6 seconds
|
||||
Timer(const Duration(seconds: 6), () {
|
||||
widget.onContinue();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
/// 🔹 Background Image
|
||||
Positioned.fill(
|
||||
child: Image.asset(
|
||||
'assets/images/kconnect2.webp',
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
|
||||
/// 🔹 Centered Text Overlay
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
Text(
|
||||
'Kconnect',
|
||||
style: TextStyle(
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
letterSpacing: 1.5,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Text(
|
||||
'Kangra Central Co-operative Bank',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: Colors.white,
|
||||
letterSpacing: 1.2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
/// 🔹 Loading Spinner at Bottom
|
||||
const Positioned(
|
||||
bottom: 40,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*import 'package:flutter/material.dart';
|
||||
|
||||
class WelcomeScreen extends StatelessWidget {
|
||||
final VoidCallback onContinue;
|
||||
|
||||
const WelcomeScreen({super.key, required this.onContinue});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Spacer(),
|
||||
const Text(
|
||||
'Welcome to',
|
||||
style: TextStyle(fontSize: 28, color: Colors.black87),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const Text(
|
||||
'KCCB',
|
||||
style: TextStyle(
|
||||
fontSize: 42,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.indigo,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
Image.asset(
|
||||
'assets/images/logo.png',
|
||||
width: 150,
|
||||
height: 150,
|
||||
),
|
||||
const Spacer(),
|
||||
ElevatedButton(
|
||||
onPressed: onContinue,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.indigo,
|
||||
foregroundColor: Colors.white,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 28, vertical: 20),
|
||||
),
|
||||
child: const Text('Proceed to Login'),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user