Compare commits
No commits in common. "login-screen" and "main" have entirely different histories.
login-scre
...
main
@ -1,6 +1,4 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||
<application
|
||||
android:label="kmobile"
|
||||
android:name="${applicationName}"
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 26 KiB |
Binary file not shown.
Before Width: | Height: | Size: 5.7 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 26 KiB |
@ -45,7 +45,5 @@
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
<string>We use Face ID to secure your data.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -1,20 +1,14 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../app.dart';
|
||||
import '../../features/auth/models/auth_token.dart';
|
||||
import '../../features/auth/models/auth_credentials.dart';
|
||||
import '../../data/models/user.dart';
|
||||
import '../../core/errors/exceptions.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
|
||||
import '../../features/dashboard/screens/dashboard_screen.dart';
|
||||
|
||||
class AuthService {
|
||||
final Dio _dio;
|
||||
|
||||
AuthService(this._dio);
|
||||
|
||||
Future<AuthToken> login(AuthCredentials credentials) async {
|
||||
|
103
lib/app.dart
103
lib/app.dart
@ -1,20 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:kmobile/features/auth/screens/customer_info_screen.dart';
|
||||
import 'package:kmobile/security/secure_storage.dart';
|
||||
import 'api/services/auth_service.dart';
|
||||
import 'config/themes.dart';
|
||||
import 'config/routes.dart';
|
||||
import 'data/repositories/auth_repository.dart';
|
||||
import 'di/injection.dart';
|
||||
import 'features/auth/controllers/auth_cubit.dart';
|
||||
import 'features/auth/controllers/auth_state.dart';
|
||||
import 'features/auth/screens/Card_screen.dart';
|
||||
import 'features/auth/screens/login_screen.dart';
|
||||
import 'features/auth/screens/service_screen.dart';
|
||||
import 'features/dashboard/screens/dashboard_screen.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
|
||||
class KMobile extends StatelessWidget {
|
||||
const KMobile({super.key});
|
||||
@ -59,12 +52,8 @@ class KMobile extends StatelessWidget {
|
||||
return const _SplashScreen();
|
||||
}
|
||||
|
||||
if(state is ShowBiometricPermission){
|
||||
return const BiometricPermissionScreen();
|
||||
}
|
||||
|
||||
if (state is Authenticated) {
|
||||
return const NavigationScaffold();
|
||||
return const DashboardScreen();
|
||||
}
|
||||
|
||||
return const LoginScreen();
|
||||
@ -115,93 +104,3 @@ class _SplashScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NavigationScaffold extends StatefulWidget {
|
||||
const NavigationScaffold({super.key});
|
||||
|
||||
@override
|
||||
State<NavigationScaffold> createState() => _NavigationScaffoldState();
|
||||
}
|
||||
|
||||
class _NavigationScaffoldState extends State<NavigationScaffold> {
|
||||
final PageController _pageController = PageController();
|
||||
int _selectedIndex = 0;
|
||||
|
||||
final List<Widget> _pages = [
|
||||
DashboardScreen(),
|
||||
CardScreen(),
|
||||
ServiceScreen(),
|
||||
CustomerInfoScreen()
|
||||
];
|
||||
|
||||
void _onItemTapped(int index) {
|
||||
setState(() {
|
||||
_selectedIndex = index;
|
||||
});
|
||||
_pageController.jumpToPage(index);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: PageView(
|
||||
controller: _pageController,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: _pages,
|
||||
),
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
currentIndex: _selectedIndex,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
items: const [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.home_filled),
|
||||
label: 'Home',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Symbols.credit_card),
|
||||
label: 'Card',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Symbols.concierge),
|
||||
label: 'Services',
|
||||
),
|
||||
],
|
||||
onTap: _onItemTapped,
|
||||
backgroundColor: const Color(0xFFE0F7FA), // Light blue background
|
||||
selectedItemColor: Colors.blue[800],
|
||||
unselectedItemColor: Colors.black54,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BiometricPermissionScreen extends StatelessWidget {
|
||||
const BiometricPermissionScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cubit = context.read<AuthCubit>();
|
||||
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text('Would you like to enable biometric authentication?'),
|
||||
ElevatedButton(
|
||||
onPressed: () => cubit.handleBiometricChoice(true),
|
||||
child: const Text('Yes'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => cubit.handleBiometricChoice(false),
|
||||
child: const Text('No, thanks'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -1,7 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:kmobile/features/auth/screens/customer_info_screen.dart';
|
||||
import 'package:kmobile/features/auth/screens/mpin_screen.dart';
|
||||
import '../app.dart';
|
||||
import '../features/auth/screens/login_screen.dart';
|
||||
// import '../features/auth/screens/forgot_password_screen.dart';
|
||||
// import '../features/auth/screens/register_screen.dart';
|
||||
@ -18,15 +15,12 @@ class AppRoutes {
|
||||
// Route names
|
||||
static const String splash = '/';
|
||||
static const String login = '/login';
|
||||
static const String mPin = '/mPin';
|
||||
static const String register = '/register';
|
||||
static const String forgotPassword = '/forgot-password';
|
||||
static const String navigationScaffold = '/navigation-scaffold';
|
||||
static const String dashboard = '/dashboard';
|
||||
static const String accounts = '/accounts';
|
||||
static const String transactions = '/transactions';
|
||||
static const String payments = '/payments';
|
||||
static const String customer_info = '/customer-info';
|
||||
|
||||
// Route generator
|
||||
static Route<dynamic> generateRoute(RouteSettings settings) {
|
||||
@ -34,9 +28,6 @@ class AppRoutes {
|
||||
case login:
|
||||
return MaterialPageRoute(builder: (_) => const LoginScreen());
|
||||
|
||||
case mPin:
|
||||
return MaterialPageRoute(builder: (_) => const MPinScreen());
|
||||
|
||||
case register:
|
||||
// Placeholder - create the RegisterScreen class and uncomment
|
||||
// return MaterialPageRoute(builder: (_) => const RegisterScreen());
|
||||
@ -47,15 +38,9 @@ class AppRoutes {
|
||||
// return MaterialPageRoute(builder: (_) => const ForgotPasswordScreen());
|
||||
return _errorRoute();
|
||||
|
||||
case navigationScaffold:
|
||||
return MaterialPageRoute(builder: (_) => const NavigationScaffold());
|
||||
|
||||
case dashboard:
|
||||
return MaterialPageRoute(builder: (_) => const DashboardScreen());
|
||||
|
||||
case customer_info:
|
||||
return MaterialPageRoute(builder: (_) => const CustomerInfoScreen());
|
||||
|
||||
case accounts:
|
||||
// Placeholder - create the AccountsScreen class and uncomment
|
||||
// return MaterialPageRoute(builder: (_) => const AccountsScreen());
|
||||
|
@ -22,61 +22,51 @@ class AppThemes {
|
||||
fontSize: 96,
|
||||
fontWeight: FontWeight.w300,
|
||||
color: Color(0xFF212121),
|
||||
fontFamily: 'Rubik',
|
||||
),
|
||||
displayMedium: TextStyle(
|
||||
fontSize: 60,
|
||||
fontWeight: FontWeight.w300,
|
||||
color: Color(0xFF212121),
|
||||
fontFamily: 'Rubik',
|
||||
),
|
||||
displaySmall: TextStyle(
|
||||
fontSize: 48,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Color(0xFF212121),
|
||||
fontFamily: 'Rubik',
|
||||
),
|
||||
headlineMedium: TextStyle(
|
||||
fontSize: 34,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Color(0xFF212121),
|
||||
fontFamily: 'Rubik',
|
||||
),
|
||||
headlineSmall: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Color(0xFF212121),
|
||||
fontFamily: 'Rubik',
|
||||
),
|
||||
titleLarge: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF212121),
|
||||
fontFamily: 'Rubik',
|
||||
),
|
||||
bodyLarge: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Color(0xFF212121),
|
||||
fontFamily: 'Rubik',
|
||||
),
|
||||
bodyMedium: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Color(0xFF212121),
|
||||
fontFamily: 'Rubik',
|
||||
),
|
||||
bodySmall: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Color(0xFF757575),
|
||||
fontFamily: 'Rubik',
|
||||
),
|
||||
labelLarge: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF212121),
|
||||
fontFamily: 'Rubik',
|
||||
),
|
||||
);
|
||||
|
||||
@ -96,7 +86,6 @@ class AppThemes {
|
||||
// Light theme
|
||||
static final ThemeData lightTheme = ThemeData(
|
||||
useMaterial3: true,
|
||||
fontFamily: 'Rubik',
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: _primaryColorLight,
|
||||
secondary: _secondaryColorLight,
|
||||
@ -178,7 +167,6 @@ class AppThemes {
|
||||
|
||||
// Dark theme
|
||||
static final ThemeData darkTheme = ThemeData(
|
||||
fontFamily: 'Rubik',
|
||||
useMaterial3: true,
|
||||
colorScheme: const ColorScheme.dark(
|
||||
primary: _primaryColorDark,
|
||||
|
@ -1,10 +1,4 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../../../app.dart';
|
||||
import '../../../data/repositories/auth_repository.dart';
|
||||
import 'auth_state.dart';
|
||||
|
||||
@ -53,60 +47,4 @@ class AuthCubit extends Cubit<AuthState> {
|
||||
emit(AuthError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> checkFirstLaunch() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
final isFirstLaunch = prefs.getBool('isFirstLaunch') ?? true;
|
||||
|
||||
if (isFirstLaunch) {
|
||||
emit(ShowBiometricPermission());
|
||||
} else {
|
||||
// Continue to authentication logic (e.g., check token)
|
||||
emit(AuthLoading()); // or Unauthenticated/Authenticated
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> handleBiometricChoice(bool enabled) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('biometric_opt_in', enabled);
|
||||
await prefs.setBool('isFirstLaunch', false);
|
||||
|
||||
// Then continue to auth logic or home
|
||||
if (enabled) {
|
||||
authenticateBiometric(); // implement biometric logic
|
||||
} else {
|
||||
emit(Unauthenticated());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> authenticateBiometric() async {
|
||||
final LocalAuthentication auth = LocalAuthentication();
|
||||
|
||||
try {
|
||||
final isAvailable = await auth.canCheckBiometrics;
|
||||
final isDeviceSupported = await auth.isDeviceSupported();
|
||||
|
||||
if (isAvailable && isDeviceSupported) {
|
||||
final authenticated = await auth.authenticate(
|
||||
localizedReason: 'Touch the fingerprint sensor',
|
||||
options: const AuthenticationOptions(
|
||||
biometricOnly: true,
|
||||
stickyAuth: true,
|
||||
),
|
||||
);
|
||||
|
||||
if (authenticated) {
|
||||
// Continue to normal auth logic (e.g., auto login)
|
||||
emit(AuthLoading());
|
||||
await checkAuthStatus(); // Your existing method to verify token/session
|
||||
} else {
|
||||
emit(Unauthenticated());
|
||||
}
|
||||
} else {
|
||||
emit(Unauthenticated());
|
||||
}
|
||||
} catch (e) {
|
||||
emit(Unauthenticated());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,6 @@ class AuthInitial extends AuthState {}
|
||||
|
||||
class AuthLoading extends AuthState {}
|
||||
|
||||
class ShowBiometricPermission extends AuthState {}
|
||||
|
||||
class Authenticated extends AuthState {
|
||||
final User user;
|
||||
|
||||
|
@ -1,18 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CardScreen extends StatefulWidget {
|
||||
const CardScreen({super.key});
|
||||
|
||||
@override
|
||||
State<CardScreen> createState() => _CardScreen();
|
||||
}
|
||||
|
||||
class _CardScreen extends State<CardScreen>{
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,104 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
|
||||
class CustomerInfoScreen extends StatefulWidget {
|
||||
const CustomerInfoScreen({super.key});
|
||||
|
||||
@override
|
||||
State<CustomerInfoScreen> createState() => _CustomerInfoScreen();
|
||||
}
|
||||
|
||||
class _CustomerInfoScreen extends State<CustomerInfoScreen>{
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: IconButton(icon: const Icon(Symbols.arrow_back_ios_new),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},),
|
||||
title: const Text('kMobile', style: TextStyle(color: Colors.black,
|
||||
fontWeight: FontWeight.w500),),
|
||||
actions: const [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 10.0),
|
||||
child: CircleAvatar(
|
||||
backgroundImage: AssetImage('assets/images/avatar.jpg'), // Replace with your image
|
||||
radius: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
body: const SingleChildScrollView(
|
||||
physics: AlwaysScrollableScrollPhysics(),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: SafeArea(
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 30),
|
||||
CircleAvatar(
|
||||
backgroundImage: AssetImage('assets/images/avatar.jpg'), // Replace with your image
|
||||
radius: 50,
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10.0),
|
||||
child: Text('Trina Bakshi', style: TextStyle(fontSize: 20,
|
||||
color: Colors.black, fontWeight: FontWeight.w500),),
|
||||
),
|
||||
|
||||
Text('CIF: 2553677487774', style: TextStyle(fontSize: 16, color: Colors.grey),),
|
||||
SizedBox(height: 30,),
|
||||
InfoField(label: 'Number of Active Accounts', value: '3'),
|
||||
InfoField(label: 'Mobile Number', value: '987XXXXX78'),
|
||||
InfoField(label: 'Date of Birth', value: '12-07-1984'),
|
||||
InfoField(label: 'Branch', value: 'Krishnapur'),
|
||||
InfoField(label: 'Aadhar Number', value: '7665 XXXX 1276'),
|
||||
InfoField(label: 'PAN Number', value: '700127638009871'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class InfoField extends StatelessWidget {
|
||||
final String label;
|
||||
final String value;
|
||||
|
||||
const InfoField({Key? key, required this.label, required this.value}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,8 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:kmobile/features/auth/screens/mpin_screen.dart';
|
||||
import '../../../app.dart';
|
||||
import '../controllers/auth_cubit.dart';
|
||||
import '../controllers/auth_state.dart';
|
||||
import '../../dashboard/screens/dashboard_screen.dart';
|
||||
@ -16,39 +13,35 @@ class LoginScreen extends StatefulWidget {
|
||||
|
||||
class LoginScreenState extends State<LoginScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _customerNumberController = TextEditingController();
|
||||
final _usernameController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
bool _obscurePassword = true;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_customerNumberController.dispose();
|
||||
_usernameController.dispose();
|
||||
_passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _submitForm() {
|
||||
// if (_formKey.currentState!.validate()) {
|
||||
// context.read<AuthCubit>().login(
|
||||
// _customerNumberController.text.trim(),
|
||||
// _passwordController.text,
|
||||
// );
|
||||
// }
|
||||
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (context) => const MPinScreen()),
|
||||
);
|
||||
if (_formKey.currentState!.validate()) {
|
||||
context.read<AuthCubit>().login(
|
||||
_usernameController.text.trim(),
|
||||
_passwordController.text,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
// appBar: AppBar(title: const Text('Login')),
|
||||
appBar: AppBar(title: const Text('Login')),
|
||||
body: BlocConsumer<AuthCubit, AuthState>(
|
||||
listener: (context, state) {
|
||||
if (state is Authenticated) {
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (context) => const NavigationScaffold()),
|
||||
MaterialPageRoute(builder: (context) => const DashboardScreen()),
|
||||
);
|
||||
} else if (state is AuthError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
@ -63,35 +56,19 @@ class LoginScreenState extends State<LoginScreen> {
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
// crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Bank logo or app branding
|
||||
SvgPicture.asset('assets/images/kccb_logo.svg', width: 100, height: 100,),
|
||||
const SizedBox(height: 16),
|
||||
// Title
|
||||
const Text(
|
||||
'KCCB',
|
||||
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.blue),
|
||||
),
|
||||
const FlutterLogo(size: 80),
|
||||
const SizedBox(height: 48),
|
||||
|
||||
TextFormField(
|
||||
controller: _customerNumberController,
|
||||
controller: _usernameController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Customer Number',
|
||||
// prefixIcon: Icon(Icons.person),
|
||||
labelText: 'Username',
|
||||
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) {
|
||||
@ -106,17 +83,8 @@ class LoginScreenState extends State<LoginScreen> {
|
||||
controller: _passwordController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Password',
|
||||
// prefixIcon: const Icon(Icons.lock),
|
||||
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
|
||||
@ -139,72 +107,43 @@ class LoginScreenState extends State<LoginScreen> {
|
||||
},
|
||||
),
|
||||
|
||||
// Align(
|
||||
// alignment: Alignment.centerRight,
|
||||
// child: TextButton(
|
||||
// onPressed: () {
|
||||
// // Navigate to forgot password screen
|
||||
// },
|
||||
// child: const Text('Forgot Password?'),
|
||||
// ),
|
||||
// ),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
// Navigate to forgot password screen
|
||||
},
|
||||
child: const Text('Forgot Password?'),
|
||||
),
|
||||
),
|
||||
|
||||
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
|
||||
ElevatedButton(
|
||||
onPressed: state is AuthLoading ? null : _submitForm,
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
child: state is AuthLoading
|
||||
? const CircularProgressIndicator()
|
||||
: const Text('LOGIN', style: TextStyle(fontSize: 16)),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Registration option
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text("Don't have an account?"),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// Navigate to registration screen
|
||||
},
|
||||
child: const Text('Register'),
|
||||
),
|
||||
child: state is AuthLoading
|
||||
? const CircularProgressIndicator()
|
||||
: const Text('Login', style: TextStyle(fontSize: 16),),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 15),
|
||||
|
||||
// OR Divider
|
||||
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(
|
||||
onPressed: () {
|
||||
// Handle register
|
||||
},
|
||||
style: OutlinedButton.styleFrom(
|
||||
shape: const StadiumBorder(),
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
backgroundColor: Colors.lightBlue[100],
|
||||
foregroundColor: Colors.black
|
||||
),
|
||||
child: const Text('Register'),
|
||||
),
|
||||
),
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -1,146 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:kmobile/features/auth/controllers/auth_cubit.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
|
||||
import '../../../app.dart';
|
||||
|
||||
class MPinScreen extends StatefulWidget {
|
||||
const MPinScreen({super.key});
|
||||
|
||||
@override
|
||||
MPinScreenState createState() => MPinScreenState();
|
||||
}
|
||||
|
||||
class MPinScreenState extends State<MPinScreen> {
|
||||
List<String> mPin = [];
|
||||
|
||||
void addDigit(String digit) {
|
||||
if (mPin.length < 4) {
|
||||
setState(() {
|
||||
mPin.add(digit);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void deleteDigit() {
|
||||
if (mPin.isNotEmpty) {
|
||||
setState(() {
|
||||
mPin.removeLast();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const NavigationScaffold()),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text("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(Symbols.check) : Text(
|
||||
key == '<' ? '⌫' : key,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: key == 'Enter' ?
|
||||
FontWeight.normal : FontWeight.normal,
|
||||
color: key == 'Enter' ? Colors.blue : Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cubit = context.read<AuthCubit>();
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
const Spacer(),
|
||||
// Logo
|
||||
const FlutterLogo(size: 100),
|
||||
const SizedBox(height: 20),
|
||||
const Text(
|
||||
"Enter your mPIN",
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
buildMPinDots(),
|
||||
const Spacer(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
TextButton(onPressed: () {
|
||||
cubit.authenticateBiometric();
|
||||
}, child: const Text("Try another way")),
|
||||
TextButton(onPressed: () {}, child: const Text("Register?")),
|
||||
],
|
||||
),
|
||||
buildNumberPad(),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ServiceScreen extends StatefulWidget {
|
||||
const ServiceScreen({super.key});
|
||||
|
||||
@override
|
||||
State<ServiceScreen> createState() => _ServiceScreen();
|
||||
}
|
||||
|
||||
class _ServiceScreen extends State<ServiceScreen>{
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
||||
);
|
||||
}
|
||||
}
|
@ -1,6 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:kmobile/features/auth/screens/customer_info_screen.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../auth/controllers/auth_cubit.dart';
|
||||
import '../../auth/controllers/auth_state.dart';
|
||||
import '../widgets/account_card.dart';
|
||||
import '../widgets/transaction_list_item.dart';
|
||||
import '../../accounts/models/account.dart';
|
||||
import '../../transactions/models/transaction.dart';
|
||||
|
||||
class DashboardScreen extends StatefulWidget {
|
||||
const DashboardScreen({super.key});
|
||||
@ -10,150 +15,331 @@ class DashboardScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DashboardScreenState extends State<DashboardScreen> {
|
||||
// Mock data for transactions
|
||||
final List<Map<String, String>> transactions = [
|
||||
{'name': 'Raj Kumar', 'amount': '₹1,000', 'date': '09 March, 2025 16:04', 'type': 'in'},
|
||||
{'name': 'Sunita Joshi', 'amount': '₹1,45,000', 'date': '07 March, 2025 16:04', 'type': 'out'},
|
||||
{'name': 'Manoj Singh', 'amount': '₹2,400', 'date': '07 March, 2025 16:04', 'type': 'in'},
|
||||
{'name': 'Raj Kumar', 'amount': '₹11,500', 'date': '09 March, 2025 16:04', 'type': 'in'},
|
||||
{'name': 'Manoj Singh', 'amount': '₹1,000', 'date': '', 'type': 'in'},
|
||||
// Mock data for demonstration
|
||||
final List<Account> _accounts = [
|
||||
Account(
|
||||
id: '1',
|
||||
accountNumber: '**** 4589',
|
||||
accountType: 'Savings',
|
||||
balance: 12450.75,
|
||||
currency: 'USD',
|
||||
),
|
||||
Account(
|
||||
id: '2',
|
||||
accountNumber: '**** 7823',
|
||||
accountType: 'Checking',
|
||||
balance: 3840.50,
|
||||
currency: 'USD',
|
||||
),
|
||||
];
|
||||
|
||||
final List<Transaction> _recentTransactions = [
|
||||
Transaction(
|
||||
id: 't1',
|
||||
description: 'Coffee Shop',
|
||||
amount: -4.50,
|
||||
date: DateTime.now().subtract(const Duration(hours: 3)),
|
||||
category: 'Food & Drink',
|
||||
),
|
||||
Transaction(
|
||||
id: 't2',
|
||||
description: 'Salary Deposit',
|
||||
amount: 2500.00,
|
||||
date: DateTime.now().subtract(const Duration(days: 2)),
|
||||
category: 'Income',
|
||||
),
|
||||
Transaction(
|
||||
id: 't3',
|
||||
description: 'Electric Bill',
|
||||
amount: -85.75,
|
||||
date: DateTime.now().subtract(const Duration(days: 3)),
|
||||
category: 'Utilities',
|
||||
),
|
||||
Transaction(
|
||||
id: 't4',
|
||||
description: 'Amazon Purchase',
|
||||
amount: -32.50,
|
||||
date: DateTime.now().subtract(const Duration(days: 5)),
|
||||
category: 'Shopping',
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
title: const Text('kMobile', style: TextStyle(color: Colors.blueAccent,
|
||||
fontWeight: FontWeight.w500),),
|
||||
title: const Text('Dashboard'),
|
||||
actions: [
|
||||
// IconButton(
|
||||
// icon: const Icon(Icons.notifications_outlined),
|
||||
// onPressed: () {
|
||||
// // Navigate to notifications
|
||||
// },
|
||||
// ),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10.0),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
onTap: (){
|
||||
// Navigator.push(context, MaterialPageRoute(
|
||||
// builder: (context) => const CustomerInfoScreen()));
|
||||
},
|
||||
child: const CircleAvatar(
|
||||
backgroundImage: AssetImage('assets/images/avatar.jpg'), // Replace with your image
|
||||
radius: 20,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.notifications_outlined),
|
||||
onPressed: () {
|
||||
// Navigate to notifications
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.logout),
|
||||
onPressed: () {
|
||||
_showLogoutConfirmation(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Account Info Card
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue[700],
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
// Implement refresh logic to fetch updated data
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Greeting section
|
||||
BlocBuilder<AuthCubit, AuthState>(
|
||||
builder: (context, state) {
|
||||
if (state is Authenticated) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Hello, ${state.user.username}',
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Welcome back to your banking app',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
child: const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Account cards section
|
||||
const Text(
|
||||
'Your Accounts',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
height: 180,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: _accounts.length + 1, // +1 for the "Add Account" card
|
||||
itemBuilder: (context, index) {
|
||||
if (index < _accounts.length) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: AccountCard(account: _accounts[index]),
|
||||
);
|
||||
} else {
|
||||
// "Add Account" card
|
||||
return Container(
|
||||
width: 300,
|
||||
margin: const EdgeInsets.only(right: 16.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.grey[300]!),
|
||||
),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.add_circle_outline,
|
||||
size: 40,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Add New Account',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Quick Actions section
|
||||
const Text(
|
||||
'Quick Actions',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text("Account Number: ", style:
|
||||
TextStyle(color: Colors.white, fontSize: 12)),
|
||||
Text("03000156789462302", style:
|
||||
TextStyle(color: Colors.white, fontSize: 14)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 5.0),
|
||||
child: Icon(Symbols.keyboard_arrow_down, color: Colors.white),
|
||||
),
|
||||
],
|
||||
_buildQuickActionButton(
|
||||
icon: Icons.swap_horiz,
|
||||
label: 'Transfer',
|
||||
onTap: () {
|
||||
// Navigate to transfer screen
|
||||
},
|
||||
),
|
||||
SizedBox(height: 15),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text("₹ *****", style: TextStyle(color: Colors.white,
|
||||
fontSize: 24, fontWeight: FontWeight.w700)),
|
||||
Icon(Symbols.visibility_lock, color: Colors.white),
|
||||
],
|
||||
_buildQuickActionButton(
|
||||
icon: Icons.payment,
|
||||
label: 'Pay Bills',
|
||||
onTap: () {
|
||||
// Navigate to bill payment screen
|
||||
},
|
||||
),
|
||||
_buildQuickActionButton(
|
||||
icon: Icons.qr_code_scanner,
|
||||
label: 'Scan & Pay',
|
||||
onTap: () {
|
||||
// Navigate to QR code scanner
|
||||
},
|
||||
),
|
||||
_buildQuickActionButton(
|
||||
icon: Icons.more_horiz,
|
||||
label: 'More',
|
||||
onTap: () {
|
||||
// Show more options
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
const Text('Quick Links', style: TextStyle(fontSize: 15),),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Quick Links
|
||||
GridView.count(
|
||||
crossAxisCount: 4,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
_buildQuickLink(Symbols.id_card, "Customer \n Info", () {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
builder: (context) => const CustomerInfoScreen()));;
|
||||
}),
|
||||
_buildQuickLink(Symbols.currency_rupee, "Quick \n Pay",
|
||||
() => print("")),
|
||||
_buildQuickLink(Symbols.send_money, "Fund \n Transfer",
|
||||
() => print("")),
|
||||
_buildQuickLink(Symbols.server_person, "Account \n Info",
|
||||
() => print("")),
|
||||
_buildQuickLink(Symbols.receipt_long, "Account \n History",
|
||||
() => print("")),
|
||||
_buildQuickLink(Symbols.checkbook, "Handle \n Cheque",
|
||||
() => print("")),
|
||||
_buildQuickLink(Icons.group, "Manage \n Beneficiary",
|
||||
() => print("")),
|
||||
_buildQuickLink(Symbols.support_agent, "Contact \n Us",
|
||||
() => print("")),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Recent Transactions
|
||||
const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text("Recent Transactions", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
...transactions.map((tx) => ListTile(
|
||||
leading: Icon(tx['type'] == 'in' ? Symbols.call_received : Symbols.call_made, color: tx['type'] == 'in' ? Colors.green : Colors.red),
|
||||
title: Text(tx['name']!),
|
||||
subtitle: Text(tx['date']!),
|
||||
trailing: Text(tx['amount']!),
|
||||
)),
|
||||
|
||||
|
||||
],
|
||||
// Recent Transactions section
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'Recent Transactions',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// Navigate to all transactions
|
||||
},
|
||||
child: const Text('See All'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ListView.builder(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemCount: _recentTransactions.length,
|
||||
itemBuilder: (context, index) {
|
||||
return TransactionListItem(
|
||||
transaction: _recentTransactions[index],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
currentIndex: 0,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
items: const [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.dashboard),
|
||||
label: 'Home',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.account_balance_wallet),
|
||||
label: 'Accounts',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.show_chart),
|
||||
label: 'Insights',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.person),
|
||||
label: 'Profile',
|
||||
),
|
||||
],
|
||||
onTap: (index) {
|
||||
// Handle navigation
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQuickLink(IconData icon, String label, VoidCallback onTap) {
|
||||
return InkWell(
|
||||
Widget _buildQuickActionButton({
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, size: 30, color: Colors.blue[700]),
|
||||
const SizedBox(height: 4),
|
||||
Text(label, textAlign: TextAlign.center, style: const TextStyle(fontSize: 12)),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColor.withAlpha((0.1 * 255).toInt()),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: Theme.of(context).primaryColor,
|
||||
size: 28,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showLogoutConfirmation(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Logout'),
|
||||
content: const Text('Are you sure you want to logout?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('CANCEL'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
context.read<AuthCubit>().logout();
|
||||
},
|
||||
child: const Text('LOGOUT'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
212
pubspec.lock
212
pubspec.lock
@ -1,14 +1,6 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -33,14 +25,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
chalkdart:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: chalkdart
|
||||
sha256: "7ffc6bd39c81453fb9ba8dbce042a9c960219b75ea1c07196a7fa41c2fab9e86"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -113,14 +97,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -142,14 +118,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.28"
|
||||
flutter_secure_storage:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -198,14 +166,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
flutter_svg:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: d44bf546b13025ec7353091516f6881f1d4c633993cb109c3916c3a0159dadf1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@ -224,22 +184,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.3"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -296,46 +240,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
local_auth:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: local_auth
|
||||
sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
local_auth_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: local_auth_android
|
||||
sha256: "63ad7ca6396290626dc0cb34725a939e4cfe965d80d36112f08d49cf13a8136e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.49"
|
||||
local_auth_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: local_auth_darwin
|
||||
sha256: "630996cd7b7f28f5ab92432c4b35d055dd03a747bc319e5ffbb3c4806a3e50d2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.3"
|
||||
local_auth_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: local_auth_platform_interface
|
||||
sha256: "1b842ff177a7068442eae093b64abe3592f816afd2a533c0ebcdbe40f9d2075a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.10"
|
||||
local_auth_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: local_auth_windows
|
||||
sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.11"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -352,14 +256,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
material_symbols_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: material_symbols_icons
|
||||
sha256: d45b6c36c3effa8cb51b1afb8698107d5ff1f88fa4631428f34a8a01abc295d7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2815.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -384,14 +280,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_parsing
|
||||
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -404,10 +292,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
|
||||
sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.17"
|
||||
version: "2.2.16"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -440,14 +328,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -472,62 +352,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.3"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.10"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.4"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -589,30 +413,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
vector_graphics:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics
|
||||
sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.18"
|
||||
vector_graphics_codec:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_codec
|
||||
sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.13"
|
||||
vector_graphics_compiler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_compiler
|
||||
sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.16"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -653,14 +453,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.5.0"
|
||||
sdks:
|
||||
dart: ">=3.7.0 <4.0.0"
|
||||
flutter: ">=3.27.0"
|
||||
|
21
pubspec.yaml
21
pubspec.yaml
@ -42,10 +42,6 @@ dependencies:
|
||||
flutter_bloc: ^9.1.0
|
||||
get_it: ^8.0.3
|
||||
intl: any
|
||||
flutter_svg: ^2.1.0
|
||||
local_auth: ^2.3.0
|
||||
material_symbols_icons: ^4.2815.0
|
||||
shared_preferences: ^2.5.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -70,9 +66,9 @@ flutter:
|
||||
uses-material-design: true
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
assets:
|
||||
- assets/images/kccb_logo.svg
|
||||
- assets/images/avatar.jpg
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||
@ -85,12 +81,11 @@ flutter:
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
fonts:
|
||||
- family: Rubik
|
||||
fonts:
|
||||
- asset: assets/fonts/Rubik-Regular.ttf
|
||||
- asset: assets/fonts/Rubik-Bold.ttf
|
||||
weight: 700
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
|
Loading…
x
Reference in New Issue
Block a user