Compare commits

...

7 Commits

32 changed files with 1631 additions and 373 deletions

View File

@ -1,4 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <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 <application
android:label="kmobile" android:label="kmobile"
android:name="${applicationName}" android:name="${applicationName}"

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen --> <!-- Modify this file to customize your launch splash screens -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" /> <item android:drawable="?android:colorBackground" />

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen --> <!-- Modify this file to customize your launch splash screens -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" /> <item android:drawable="@android:color/white" />

View File

@ -2,7 +2,7 @@
<resources> <resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on --> <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when <!-- Show a splash screens on the activity. Automatically removed when
the Flutter engine draws its first frame --> the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
</style> </style>

View File

@ -2,7 +2,7 @@
<resources> <resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off --> <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar"> <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when <!-- Show a splash screens on the activity. Automatically removed when
the Flutter engine draws its first frame --> the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
</style> </style>

BIN
assets/fonts/Rubik-Bold.ttf Normal file

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

After

Width:  |  Height:  |  Size: 26 KiB

BIN
assets/images/avatar.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -45,5 +45,7 @@
<true/> <true/>
<key>UIApplicationSupportsIndirectInputEvents</key> <key>UIApplicationSupportsIndirectInputEvents</key>
<true/> <true/>
<key>NSFaceIDUsageDescription</key>
<string>We use Face ID to secure your data.</string>
</dict> </dict>
</plist> </plist>

View File

@ -1,14 +1,20 @@
import 'dart:developer'; import 'dart:developer';
import 'package:dio/dio.dart'; 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_token.dart';
import '../../features/auth/models/auth_credentials.dart'; import '../../features/auth/models/auth_credentials.dart';
import '../../data/models/user.dart'; import '../../data/models/user.dart';
import '../../core/errors/exceptions.dart'; import '../../core/errors/exceptions.dart';
import 'package:local_auth/local_auth.dart';
import '../../features/dashboard/screens/dashboard_screen.dart';
class AuthService { class AuthService {
final Dio _dio; final Dio _dio;
AuthService(this._dio); AuthService(this._dio);
Future<AuthToken> login(AuthCredentials credentials) async { Future<AuthToken> login(AuthCredentials credentials) async {

View File

@ -1,13 +1,20 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kmobile/features/customer_info/screens/customer_info_screen.dart';
import 'package:kmobile/security/secure_storage.dart';
import 'api/services/auth_service.dart';
import 'config/themes.dart'; import 'config/themes.dart';
import 'config/routes.dart'; import 'config/routes.dart';
import 'data/repositories/auth_repository.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/auth/controllers/auth_state.dart'; import 'features/auth/controllers/auth_state.dart';
import 'features/card/screens/Card_screen.dart';
import 'features/auth/screens/login_screen.dart'; import 'features/auth/screens/login_screen.dart';
import 'features/service/screens/service_screen.dart';
import 'features/dashboard/screens/dashboard_screen.dart'; import 'features/dashboard/screens/dashboard_screen.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
class KMobile extends StatelessWidget { class KMobile extends StatelessWidget {
const KMobile({super.key}); const KMobile({super.key});
@ -52,8 +59,12 @@ class KMobile extends StatelessWidget {
return const _SplashScreen(); return const _SplashScreen();
} }
if(state is ShowBiometricPermission){
return const BiometricPermissionScreen();
}
if (state is Authenticated) { if (state is Authenticated) {
return const DashboardScreen(); return const NavigationScaffold();
} }
return const LoginScreen(); return const LoginScreen();
@ -64,7 +75,7 @@ class KMobile extends StatelessWidget {
} }
} }
// Simple splash screen component // Simple splash screens component
class _SplashScreen extends StatelessWidget { class _SplashScreen extends StatelessWidget {
const _SplashScreen(); const _SplashScreen();
@ -104,3 +115,93 @@ 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'),
),
],
),
),
);
}
}

View File

@ -1,4 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:kmobile/features/customer_info/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/login_screen.dart';
// import '../features/auth/screens/forgot_password_screen.dart'; // import '../features/auth/screens/forgot_password_screen.dart';
// import '../features/auth/screens/register_screen.dart'; // import '../features/auth/screens/register_screen.dart';
@ -15,12 +18,15 @@ class AppRoutes {
// Route names // Route names
static const String splash = '/'; static const String splash = '/';
static const String login = '/login'; static const String login = '/login';
static const String mPin = '/mPin';
static const String register = '/register'; static const String register = '/register';
static const String forgotPassword = '/forgot-password'; static const String forgotPassword = '/forgot-password';
static const String navigationScaffold = '/navigation-scaffold';
static const String dashboard = '/dashboard'; static const String dashboard = '/dashboard';
static const String accounts = '/accounts'; static const String accounts = '/accounts';
static const String transactions = '/transactions'; static const String transactions = '/transactions';
static const String payments = '/payments'; static const String payments = '/payments';
static const String customer_info = '/customer-info';
// Route generator // Route generator
static Route<dynamic> generateRoute(RouteSettings settings) { static Route<dynamic> generateRoute(RouteSettings settings) {
@ -28,6 +34,9 @@ class AppRoutes {
case login: case login:
return MaterialPageRoute(builder: (_) => const LoginScreen()); return MaterialPageRoute(builder: (_) => const LoginScreen());
case mPin:
return MaterialPageRoute(builder: (_) => const MPinScreen());
case register: case register:
// Placeholder - create the RegisterScreen class and uncomment // Placeholder - create the RegisterScreen class and uncomment
// return MaterialPageRoute(builder: (_) => const RegisterScreen()); // return MaterialPageRoute(builder: (_) => const RegisterScreen());
@ -38,9 +47,15 @@ class AppRoutes {
// return MaterialPageRoute(builder: (_) => const ForgotPasswordScreen()); // return MaterialPageRoute(builder: (_) => const ForgotPasswordScreen());
return _errorRoute(); return _errorRoute();
case navigationScaffold:
return MaterialPageRoute(builder: (_) => const NavigationScaffold());
case dashboard: case dashboard:
return MaterialPageRoute(builder: (_) => const DashboardScreen()); return MaterialPageRoute(builder: (_) => const DashboardScreen());
case customer_info:
return MaterialPageRoute(builder: (_) => const CustomerInfoScreen());
case accounts: case accounts:
// Placeholder - create the AccountsScreen class and uncomment // Placeholder - create the AccountsScreen class and uncomment
// return MaterialPageRoute(builder: (_) => const AccountsScreen()); // return MaterialPageRoute(builder: (_) => const AccountsScreen());

View File

@ -22,51 +22,61 @@ class AppThemes {
fontSize: 96, fontSize: 96,
fontWeight: FontWeight.w300, fontWeight: FontWeight.w300,
color: Color(0xFF212121), color: Color(0xFF212121),
fontFamily: 'Rubik',
), ),
displayMedium: TextStyle( displayMedium: TextStyle(
fontSize: 60, fontSize: 60,
fontWeight: FontWeight.w300, fontWeight: FontWeight.w300,
color: Color(0xFF212121), color: Color(0xFF212121),
fontFamily: 'Rubik',
), ),
displaySmall: TextStyle( displaySmall: TextStyle(
fontSize: 48, fontSize: 48,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: Color(0xFF212121), color: Color(0xFF212121),
fontFamily: 'Rubik',
), ),
headlineMedium: TextStyle( headlineMedium: TextStyle(
fontSize: 34, fontSize: 34,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: Color(0xFF212121), color: Color(0xFF212121),
fontFamily: 'Rubik',
), ),
headlineSmall: TextStyle( headlineSmall: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: Color(0xFF212121), color: Color(0xFF212121),
fontFamily: 'Rubik',
), ),
titleLarge: TextStyle( titleLarge: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF212121), color: Color(0xFF212121),
fontFamily: 'Rubik',
), ),
bodyLarge: TextStyle( bodyLarge: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: Color(0xFF212121), color: Color(0xFF212121),
fontFamily: 'Rubik',
), ),
bodyMedium: TextStyle( bodyMedium: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: Color(0xFF212121), color: Color(0xFF212121),
fontFamily: 'Rubik',
), ),
bodySmall: TextStyle( bodySmall: TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: Color(0xFF757575), color: Color(0xFF757575),
fontFamily: 'Rubik',
), ),
labelLarge: TextStyle( labelLarge: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF212121), color: Color(0xFF212121),
fontFamily: 'Rubik',
), ),
); );
@ -86,6 +96,7 @@ class AppThemes {
// Light theme // Light theme
static final ThemeData lightTheme = ThemeData( static final ThemeData lightTheme = ThemeData(
useMaterial3: true, useMaterial3: true,
fontFamily: 'Rubik',
colorScheme: const ColorScheme.light( colorScheme: const ColorScheme.light(
primary: _primaryColorLight, primary: _primaryColorLight,
secondary: _secondaryColorLight, secondary: _secondaryColorLight,
@ -167,6 +178,7 @@ class AppThemes {
// Dark theme // Dark theme
static final ThemeData darkTheme = ThemeData( static final ThemeData darkTheme = ThemeData(
fontFamily: 'Rubik',
useMaterial3: true, useMaterial3: true,
colorScheme: const ColorScheme.dark( colorScheme: const ColorScheme.dark(
primary: _primaryColorDark, primary: _primaryColorDark,

View File

@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
class AccountInfoScreen extends StatefulWidget {
const AccountInfoScreen({super.key});
@override
State<AccountInfoScreen> createState() => _AccountInfoScreen();
}
class _AccountInfoScreen extends State<AccountInfoScreen>{
@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('Account Info', style: TextStyle(color: Colors.black,
fontWeight: FontWeight.w500),),
centerTitle: false,
actions: const [
Padding(
padding: EdgeInsets.only(right: 10.0),
child: CircleAvatar(
backgroundImage: AssetImage('assets/images/avatar.jpg'), // Replace with your image
radius: 20,
),
),
],
),
body: ListView(
padding: const EdgeInsets.all(16.0),
children: const [
InfoRow(title: 'Account Number', value: '700127638009871'),
InfoRow(title: 'Nominee Customer No', value: '700127638009871'),
InfoRow(title: 'SMS Service', value: 'Active'),
InfoRow(title: 'Missed Call Service', value: 'Active'),
InfoRow(title: 'Customer Number', value: '9000875272000212'),
InfoRow(title: 'Product Name', value: 'SAVINGS-PERSONAL'),
InfoRow(title: 'Account Opening Date', value: '12-09-2012'),
InfoRow(title: 'Account Status', value: 'OPEN'),
InfoRow(title: 'Available Balance', value: '12,000 CR'),
InfoRow(title: 'Interest Rate', value: '12.00'),
],
),
);
}
}
class InfoRow extends StatelessWidget {
final String title;
final String value;
const InfoRow({required this.title, required this.value, super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 14)),
const SizedBox(height: 4),
Text(value, style: const TextStyle(fontSize: 14)),
const SizedBox(height: 10),
],
),
);
}
}

View File

@ -0,0 +1,183 @@
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
class AccountStatementScreen extends StatefulWidget {
const AccountStatementScreen({super.key});
@override
State<AccountStatementScreen> createState() => _AccountStatementScreen();
}
class _AccountStatementScreen extends State<AccountStatementScreen>{
DateTimeRange? selectedDateRange;
final _amountRangeController = TextEditingController(text: "100-500");
final transactions = [
{"desc": "Transfer From ICICI Bank subsidy", "amount": "+₹133.98", "type": "Cr", "time": "21-02-2024 13:09:30"},
{"desc": "Mobile recharge", "amount": "-₹299.00", "type": "Dr", "time": "21-02-2024 13:09:30"},
{"desc": "NEFT received from Jaya Saha", "amount": "+₹987.80", "type": "Cr", "time": "21-02-2024 13:09:30"},
{"desc": "Transfer From ICICI Bank subsidy", "amount": "+₹100.00", "type": "Cr", "time": "21-02-2024 13:09:30"},
{"desc": "Transfer From ICICI Bank subsidy", "amount": "-₹100.00", "type": "Dr", "time": "21-02-2024 13:09:30"},
{"desc": "Transfer From ICICI Bank subsidy", "amount": "+₹100.00", "type": "Cr", "time": "21-02-2024 13:09:30"},
{"desc": "Transfer From ICICI Bank subsidy", "amount": "+₹100.00", "type": "Cr", "time": "21-02-2024 13:09:30"},
];
Future<void> _selectDateRange(BuildContext context) async {
final DateTime now = DateTime.now();
final DateTimeRange? picked = await showDateRangePicker(
context: context,
firstDate: DateTime(2023),
lastDate: now, // disables future dates
initialDateRange: selectedDateRange ??
DateTimeRange(start: now.subtract(const Duration(days: 7)), end: now),
);
if (picked != null) {
setState(() {
selectedDateRange = picked;
});
}
}
String _formatDate(DateTime date) {
return "${date.day.toString().padLeft(2, '0')}-"
"${date.month.toString().padLeft(2, '0')}-"
"${date.year}";
}
@override
void dispose() {
_amountRangeController.dispose();
super.dispose();
}
@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('Account Statement', style: TextStyle(color: Colors.black,
fontWeight: FontWeight.w500),),
centerTitle: false,
actions: const [
Padding(
padding: EdgeInsets.only(right: 10.0),
child: CircleAvatar(
backgroundImage: AssetImage('assets/images/avatar.jpg'), // Replace with your image
radius: 20,
),
),
],
),
body: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Filters', style: TextStyle(fontSize: 17),),
const SizedBox(height: 15,),
Row(
children: [
Expanded(child: _buildDateRangeSelector()),
const SizedBox(width: 10),
Expanded(child: _buildFilterBox("100-500")),
],
),
const SizedBox(height: 35),
Expanded(
child: ListView.builder(
itemCount: transactions.length,
itemBuilder: (context, index) {
final txn = transactions[index];
return _buildTransactionTile(txn);
},
),
),
],
),
),
);
}
Widget _buildDateRangeSelector() {
String label = "Select Date";
if (selectedDateRange != null) {
final from = _formatDate(selectedDateRange!.start);
final to = _formatDate(selectedDateRange!.end);
label = "$from - $to";
}
return GestureDetector(
onTap: () => _selectDateRange(context),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Expanded(child: Text(label, style: const TextStyle(fontSize: 16))),
const Icon(Icons.calendar_today),
],
),
),
);
}
Widget _buildFilterBox(String text) {
return TextFormField(
controller: _amountRangeController,
decoration: const InputDecoration(
labelText: 'Amount Range',
// 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 amount range';
}
return null;
},
);
}
Widget _buildTransactionTile(Map<String, String> txn) {
final isCredit = txn['type'] == "Cr";
final amountColor = isCredit ? Colors.green : Colors.red;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(txn['time']!, style: const TextStyle(color: Colors.grey)),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: Text(txn['desc']!, style: const TextStyle(fontSize: 16))),
Text(
"${txn['amount']} ${txn['type']}",
style: TextStyle(color: amountColor, fontWeight: FontWeight.bold),
),
],
),
const SizedBox(height: 25),
],
);
}
}

View File

@ -1,4 +1,10 @@
import 'package:bloc/bloc.dart'; 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 '../../../data/repositories/auth_repository.dart';
import 'auth_state.dart'; import 'auth_state.dart';
@ -47,4 +53,60 @@ class AuthCubit extends Cubit<AuthState> {
emit(AuthError(e.toString())); 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());
}
}
} }

View File

@ -10,6 +10,8 @@ class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {} class AuthLoading extends AuthState {}
class ShowBiometricPermission extends AuthState {}
class Authenticated extends AuthState { class Authenticated extends AuthState {
final User user; final User user;

View File

@ -1,5 +1,8 @@
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:flutter_svg/svg.dart';
import 'package:kmobile/features/auth/screens/mpin_screen.dart';
import '../../../app.dart';
import '../controllers/auth_cubit.dart'; import '../controllers/auth_cubit.dart';
import '../controllers/auth_state.dart'; import '../controllers/auth_state.dart';
import '../../dashboard/screens/dashboard_screen.dart'; import '../../dashboard/screens/dashboard_screen.dart';
@ -13,35 +16,39 @@ class LoginScreen extends StatefulWidget {
class LoginScreenState extends State<LoginScreen> { class LoginScreenState extends State<LoginScreen> {
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
final _usernameController = TextEditingController(); final _customerNumberController = TextEditingController();
final _passwordController = TextEditingController(); final _passwordController = TextEditingController();
bool _obscurePassword = true; bool _obscurePassword = true;
@override @override
void dispose() { void dispose() {
_usernameController.dispose(); _customerNumberController.dispose();
_passwordController.dispose(); _passwordController.dispose();
super.dispose(); super.dispose();
} }
void _submitForm() { void _submitForm() {
if (_formKey.currentState!.validate()) { // if (_formKey.currentState!.validate()) {
context.read<AuthCubit>().login( // context.read<AuthCubit>().login(
_usernameController.text.trim(), // _customerNumberController.text.trim(),
_passwordController.text, // _passwordController.text,
// );
// }
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => const MPinScreen()),
); );
} }
}
@override @override
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: BlocConsumer<AuthCubit, AuthState>(
listener: (context, state) { listener: (context, state) {
if (state is Authenticated) { if (state is Authenticated) {
Navigator.of(context).pushReplacement( Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => const DashboardScreen()), MaterialPageRoute(builder: (context) => const NavigationScaffold()),
); );
} else if (state is AuthError) { } else if (state is AuthError) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@ -56,19 +63,35 @@ class LoginScreenState extends State<LoginScreen> {
key: _formKey, key: _formKey,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch, // crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
// Bank logo or app branding // Bank logo or app branding
const FlutterLogo(size: 80), 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 SizedBox(height: 48), const SizedBox(height: 48),
TextFormField( TextFormField(
controller: _usernameController, controller: _customerNumberController,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Username', labelText: 'Customer Number',
prefixIcon: Icon(Icons.person), // prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(), 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, textInputAction: TextInputAction.next,
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
@ -83,8 +106,17 @@ class LoginScreenState extends State<LoginScreen> {
controller: _passwordController, controller: _passwordController,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Password', labelText: 'Password',
prefixIcon: const Icon(Icons.lock), // prefixIcon: const Icon(Icons.lock),
border: const OutlineInputBorder(), 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( suffixIcon: IconButton(
icon: Icon( icon: Icon(
_obscurePassword _obscurePassword
@ -98,6 +130,7 @@ class LoginScreenState extends State<LoginScreen> {
}, },
), ),
), ),
textInputAction: TextInputAction.done,
obscureText: _obscurePassword, obscureText: _obscurePassword,
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
@ -107,43 +140,72 @@ class LoginScreenState extends State<LoginScreen> {
}, },
), ),
Align( // Align(
alignment: Alignment.centerRight, // alignment: Alignment.centerRight,
child: TextButton( // child: TextButton(
onPressed: () { // onPressed: () {
// Navigate to forgot password screen // // Navigate to forgot password screen
}, // },
child: const Text('Forgot Password?'), // child: const Text('Forgot Password?'),
), // ),
), // ),
const SizedBox(height: 24), const SizedBox(height: 24),
ElevatedButton( SizedBox(
width: 250,
child: ElevatedButton(
onPressed: state is AuthLoading ? null : _submitForm, onPressed: state is AuthLoading ? null : _submitForm,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
shape: const StadiumBorder(),
padding: const EdgeInsets.symmetric(vertical: 16), 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 child: state is AuthLoading
? const CircularProgressIndicator() ? const CircularProgressIndicator()
: const Text('LOGIN', style: TextStyle(fontSize: 16)), : const Text('Login', style: TextStyle(fontSize: 16),),
),
), ),
const SizedBox(height: 16), const SizedBox(height: 15),
// Registration option // OR Divider
Row( const Padding(
mainAxisAlignment: MainAxisAlignment.center, padding: EdgeInsets.symmetric(vertical: 16),
child: Row(
children: [ children: [
const Text("Don't have an account?"), Expanded(child: Divider()),
TextButton( Padding(
padding: EdgeInsets.symmetric(horizontal: 8),
child: Text('OR'),
),
Expanded(child: Divider()),
],
),
),
const SizedBox(height: 25),
// Register Button
SizedBox(
width: 250,
child: ElevatedButton(
onPressed: () { onPressed: () {
// Navigate to registration screen // 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'), child: const Text('Register'),
), ),
],
), ),
], ],
), ),
), ),

View File

@ -0,0 +1,146 @@
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(),
],
),
),
);
}
}

View File

@ -0,0 +1,299 @@
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
class AddBeneficiaryScreen extends StatefulWidget {
const AddBeneficiaryScreen({super.key});
@override
State<AddBeneficiaryScreen> createState() => _AddBeneficiaryScreen();
}
class _AddBeneficiaryScreen extends State<AddBeneficiaryScreen>{
final _formKey = GlobalKey<FormState>();
final TextEditingController accountNumberController = TextEditingController();
final TextEditingController nameController = TextEditingController();
final TextEditingController bankNameController = TextEditingController();
final TextEditingController branchNameController = TextEditingController();
final TextEditingController ifscController = TextEditingController();
final TextEditingController phoneController = TextEditingController();
String accountType = 'Savings';
void _submitForm() {
if (_formKey.currentState!.validate()) {
// Handle successful submission
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.grey[900],
behavior: SnackBarBehavior.floating,
margin: const EdgeInsets.all(12),
duration: const Duration(seconds: 5),
content: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Expanded(
child: Text(
'Beneficiary added successfully',
style: TextStyle(color: Colors.white),
),
),
TextButton(
onPressed: () {
// Navigate to Payment Screen or do something
},
style: TextButton.styleFrom(
foregroundColor: Colors.blue[200],
),
child: const Text('Pay Now'),
),
IconButton(
icon: const Icon(Icons.close, color: Colors.white),
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
},
),
],
),
),
);
}
}
@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('Add Beneficiary', style: TextStyle(color: Colors.black,
fontWeight: FontWeight.w500),),
centerTitle: false,
actions: const [
Padding(
padding: EdgeInsets.only(right: 10.0),
child: CircleAvatar(
backgroundImage: AssetImage('assets/images/avatar.jpg'), // Replace with your image
radius: 20,
),
),
],
),
body: SafeArea(
child: Form(
key: _formKey,
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
TextFormField(
controller: accountNumberController,
decoration: const InputDecoration(
labelText: 'Account 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.length < 10) {
return "Enter a valid account number";
}
return null;
},
),
const SizedBox(height: 24),
TextFormField(
controller: nameController,
decoration: const InputDecoration(
labelText: 'Name',
// 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),
),
),
textInputAction: TextInputAction.next,
validator: (value) =>
value == null || value.isEmpty ? "Name is required" : null,
),
const SizedBox(height: 24),
TextFormField(
controller: bankNameController,
decoration: const InputDecoration(
labelText: 'Beneficiary Bank Name',
// 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),
),
),
textInputAction: TextInputAction.next,
validator: (value) =>
value == null || value.isEmpty ? "Bank name is required" : null,
),
const SizedBox(height: 24),
TextFormField(
controller: branchNameController,
decoration: const InputDecoration(
labelText: 'Branch Name',
// 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),
),
),
textInputAction: TextInputAction.next,
validator: (value) =>
value == null || value.isEmpty ? "Branch name is required" : null,
),
const SizedBox(height: 24),
Row(
children: [
Expanded(
flex: 2,
child: TextFormField(
controller: ifscController,
decoration: const InputDecoration(
labelText: 'IFSC Code',
// 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),
),
),
textInputAction: TextInputAction.next,
validator: (value) => value == null || value.length < 5
? "Enter a valid IFSC"
: null,
),
),
const SizedBox(width: 16),
Expanded(
flex: 2,
child: DropdownButtonFormField<String>(
value: accountType,
decoration: const InputDecoration(
labelText: 'Account Type',
// 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),
),
),
items: ['Savings', 'Current']
.map((type) => DropdownMenuItem(
value: type,
child: Text(type),
))
.toList(),
onChanged: (value) {
setState(() {
accountType = value!;
});
},
),
),
],
),
const SizedBox(height: 24),
TextFormField(
controller: phoneController,
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
labelText: 'Phone',
prefixIcon: Icon(Icons.phone),
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),
),
),
textInputAction: TextInputAction.done,
validator: (value) =>
value == null || value.length != 10 ? "Enter a valid phone" : null,
),
const SizedBox(height: 35),
],
),
),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: SizedBox(
width: 250,
child: ElevatedButton(
onPressed: _submitForm,
style: ElevatedButton.styleFrom(
shape: const StadiumBorder(),
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.blue[900],
foregroundColor: Colors.white,
),
child: const Text("Add"),
),
),
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import 'package:kmobile/features/beneficiaries/screens/add_beneficiary_screen.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
class ManageBeneficiariesScreen extends StatefulWidget {
const ManageBeneficiariesScreen({super.key});
@override
State<ManageBeneficiariesScreen> createState() => _ManageBeneficiariesScreen();
}
class _ManageBeneficiariesScreen extends State<ManageBeneficiariesScreen>{
final List<Map<String, String>> beneficiaries = [
{
'bank': 'State Bank Of India',
'name': 'Trina Bakshi',
},
{
'bank': 'State Bank Of India',
'name': 'Sheetal Rao',
},
{
'bank': 'Punjab National Bank',
'name': 'Manoj Kumar',
},
{
'bank': 'State Bank Of India',
'name': 'Rohit Mehra',
},
];
@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('Beneficiaries', style: TextStyle(color: Colors.black,
fontWeight: FontWeight.w500),),
centerTitle: false,
actions: const [
Padding(
padding: EdgeInsets.only(right: 10.0),
child: CircleAvatar(
backgroundImage: AssetImage('assets/images/avatar.jpg'), // Replace with your image
radius: 20,
),
),
],
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView.builder(
itemCount: beneficiaries.length,
itemBuilder: (context, index) {
final beneficiary = beneficiaries[index];
return ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.blue,
child: Text('A')),
title: Text(beneficiary['name']!),
subtitle: Text(beneficiary['bank']!),
trailing: IconButton(
icon: const Icon(Symbols.delete_forever, color: Colors.red),
onPressed: () {
// Delete action
},
),
);
},
),
),
floatingActionButton: Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: FloatingActionButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(
builder: (context) => const AddBeneficiaryScreen()));
},
backgroundColor: Colors.grey[300],
foregroundColor: Colors.blue[900],
elevation: 5,
child: const Icon(Icons.add),
),
),
);
}
}

View File

@ -0,0 +1,18 @@
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(
);
}
}

View File

@ -0,0 +1,104 @@
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,
),
),
],
),
);
}
}

View File

@ -1,11 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:kmobile/features/accounts/screens/account_info_screen.dart';
import '../../auth/controllers/auth_cubit.dart'; import 'package:kmobile/features/accounts/screens/account_statement_screen.dart';
import '../../auth/controllers/auth_state.dart'; import 'package:kmobile/features/customer_info/screens/customer_info_screen.dart';
import '../widgets/account_card.dart'; import 'package:kmobile/features/beneficiaries/screens/manage_beneficiaries_screen.dart';
import '../widgets/transaction_list_item.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart';
import '../../accounts/models/account.dart';
import '../../transactions/models/transaction.dart';
class DashboardScreen extends StatefulWidget { class DashboardScreen extends StatefulWidget {
const DashboardScreen({super.key}); const DashboardScreen({super.key});
@ -15,331 +13,159 @@ class DashboardScreen extends StatefulWidget {
} }
class _DashboardScreenState extends State<DashboardScreen> { class _DashboardScreenState extends State<DashboardScreen> {
// Mock data for demonstration // Mock data for transactions
final List<Account> _accounts = [ final List<Map<String, String>> transactions = [
Account( {'name': 'Raj Kumar', 'amount': '₹1,000', 'date': '09 March, 2025 16:04', 'type': 'in'},
id: '1', {'name': 'Sunita Joshi', 'amount': '₹1,45,000', 'date': '07 March, 2025 16:04', 'type': 'out'},
accountNumber: '**** 4589', {'name': 'Manoj Singh', 'amount': '₹2,400', 'date': '07 March, 2025 16:04', 'type': 'in'},
accountType: 'Savings', {'name': 'Raj Kumar', 'amount': '₹11,500', 'date': '09 March, 2025 16:04', 'type': 'in'},
balance: 12450.75, {'name': 'Manoj Singh', 'amount': '₹1,000', 'date': '', 'type': 'in'},
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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Dashboard'), automaticallyImplyLeading: false,
title: const Text('kMobile', style: TextStyle(color: Colors.blueAccent,
fontWeight: FontWeight.w500),),
actions: [ actions: [
IconButton( // IconButton(
icon: const Icon(Icons.notifications_outlined), // icon: const Icon(Icons.notifications_outlined),
onPressed: () { // onPressed: () {
// Navigate to notifications // // 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.logout),
onPressed: () {
_showLogoutConfirmation(context);
},
), ),
], ],
), ),
body: RefreshIndicator( body: SingleChildScrollView(
onRefresh: () async {
// Implement refresh logic to fetch updated data
await Future.delayed(const Duration(seconds: 1));
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Greeting section const SizedBox(height: 16),
BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) { // Account Info Card
if (state is Authenticated) { Container(
return Column( padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue[700],
borderRadius: BorderRadius.circular(16),
),
child: const Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ 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();
},
),
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( Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
_buildQuickActionButton( Text("Account Number: ", style:
icon: Icons.swap_horiz, TextStyle(color: Colors.white, fontSize: 12)),
label: 'Transfer', Text("03000156789462302", style:
onTap: () { TextStyle(color: Colors.white, fontSize: 14)),
// Navigate to transfer screen Padding(
}, padding: EdgeInsets.only(left: 5.0),
), child: Icon(Symbols.keyboard_arrow_down, 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: 15),
const SizedBox(height: 32),
// Recent Transactions section
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const Text( Text("₹ *****", style: TextStyle(color: Colors.white,
'Recent Transactions', fontSize: 24, fontWeight: FontWeight.w700)),
style: TextStyle( Icon(Symbols.visibility_lock, color: Colors.white),
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
TextButton(
onPressed: () {
// Navigate to all transactions
},
child: const Text('See All'),
),
], ],
), ),
const SizedBox(height: 8), SizedBox(height: 20),
ListView.builder( ],
physics: const NeverScrollableScrollPhysics(), ),
),
const SizedBox(height: 18),
const Text('Quick Links', style: TextStyle(fontSize: 15),),
const SizedBox(height: 16),
// Quick Links
GridView.count(
crossAxisCount: 4,
shrinkWrap: true, shrinkWrap: true,
itemCount: _recentTransactions.length, physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) { children: [
return TransactionListItem( _buildQuickLink(Symbols.id_card, "Customer \n Info", () {
transaction: _recentTransactions[index], 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",
(){
Navigator.push(context, MaterialPageRoute(
builder: (context) => const AccountInfoScreen()));
}),
_buildQuickLink(Symbols.receipt_long, "Account \n History",
() {
Navigator.push(context, MaterialPageRoute(
builder: (context) => const AccountStatementScreen()));
}),
_buildQuickLink(Symbols.checkbook, "Handle \n Cheque",
() => print("")),
_buildQuickLink(Icons.group, "Manage \n Beneficiary",
() {
Navigator.push(context, MaterialPageRoute(
builder: (context) => const ManageBeneficiariesScreen()));
}),
_buildQuickLink(Symbols.support_agent, "Contact \n Us",
() => print("")),
],
), ),
const SizedBox(height: 10),
// 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']!),
)),
], ],
), ),
), ),
), ),
),
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 _buildQuickActionButton({ Widget _buildQuickLink(IconData icon, String label, VoidCallback onTap) {
required IconData icon, return InkWell(
required String label,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap, onTap: onTap,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min,
children: [ children: [
Container( Icon(icon, size: 30, color: Colors.blue[700]),
padding: const EdgeInsets.all(12), const SizedBox(height: 4),
decoration: BoxDecoration( Text(label, textAlign: TextAlign.center, style: const TextStyle(fontSize: 12)),
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'),
),
], ],
), ),
); );

View File

@ -0,0 +1,17 @@
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(
);
}
}

View File

@ -1,6 +1,14 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -25,6 +33,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.2"
chalkdart:
dependency: transitive
description:
name: chalkdart
sha256: "7ffc6bd39c81453fb9ba8dbce042a9c960219b75ea1c07196a7fa41c2fab9e86"
url: "https://pub.dev"
source: hosted
version: "3.0.5"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@ -97,6 +113,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -118,6 +142,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.2" 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: flutter_secure_storage:
dependency: "direct main" dependency: "direct main"
description: description:
@ -166,6 +198,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.2" 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: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -184,6 +224,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.0.3" 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: http_parser:
dependency: transitive dependency: transitive
description: description:
@ -240,6 +296,46 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" 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: matcher:
dependency: transitive dependency: transitive
description: description:
@ -256,6 +352,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.11.1" 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: meta:
dependency: transitive dependency: transitive
description: description:
@ -280,6 +384,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" 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: path_provider:
dependency: transitive dependency: transitive
description: description:
@ -292,10 +404,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.16" version: "2.2.17"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
@ -328,6 +440,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -352,6 +472,62 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.4" 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: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -413,6 +589,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" 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: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -453,6 +653,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
sdks: sdks:
dart: ">=3.7.0 <4.0.0" dart: ">=3.7.0 <4.0.0"
flutter: ">=3.27.0" flutter: ">=3.27.0"

View File

@ -42,6 +42,10 @@ dependencies:
flutter_bloc: ^9.1.0 flutter_bloc: ^9.1.0
get_it: ^8.0.3 get_it: ^8.0.3
intl: any 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: dev_dependencies:
flutter_test: flutter_test:
@ -66,9 +70,9 @@ flutter:
uses-material-design: true uses-material-design: true
# To add assets to your application, add an assets section, like this: # To add assets to your application, add an assets section, like this:
# assets: assets:
# - images/a_dot_burr.jpeg - assets/images/kccb_logo.svg
# - images/a_dot_ham.jpeg - assets/images/avatar.jpg
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware # https://flutter.dev/assets-and-images/#resolution-aware
@ -81,11 +85,12 @@ flutter:
# "family" key with the font family name, and a "fonts" key with a # "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For # list giving the asset and other descriptors for the font. For
# example: # example:
# fonts: fonts:
# - family: Schyler - family: Rubik
# fonts: fonts:
# - asset: fonts/Schyler-Regular.ttf - asset: assets/fonts/Rubik-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf - asset: assets/fonts/Rubik-Bold.ttf
weight: 700
# style: italic # style: italic
# - family: Trajan Pro # - family: Trajan Pro
# fonts: # fonts: