diff --git a/assets/fonts/Rubik-Bold.ttf b/assets/fonts/Rubik-Bold.ttf new file mode 100644 index 0000000..1a9693d Binary files /dev/null and b/assets/fonts/Rubik-Bold.ttf differ diff --git a/assets/fonts/Rubik-Italic.ttf b/assets/fonts/Rubik-Italic.ttf new file mode 100644 index 0000000..1683a76 Binary files /dev/null and b/assets/fonts/Rubik-Italic.ttf differ diff --git a/assets/fonts/Rubik-Medium.ttf b/assets/fonts/Rubik-Medium.ttf new file mode 100644 index 0000000..f0bd595 Binary files /dev/null and b/assets/fonts/Rubik-Medium.ttf differ diff --git a/assets/fonts/Rubik-Regular.ttf b/assets/fonts/Rubik-Regular.ttf new file mode 100644 index 0000000..8b7b632 Binary files /dev/null and b/assets/fonts/Rubik-Regular.ttf differ diff --git a/assets/fonts/Rubik-SemiBold.ttf b/assets/fonts/Rubik-SemiBold.ttf new file mode 100644 index 0000000..26f657d Binary files /dev/null and b/assets/fonts/Rubik-SemiBold.ttf differ diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 8822310..76a34ae 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:kmobile/features/auth/screens/mpin_screen.dart'; import '../features/auth/screens/login_screen.dart'; // import '../features/auth/screens/forgot_password_screen.dart'; // import '../features/auth/screens/register_screen.dart'; @@ -15,6 +16,7 @@ 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 dashboard = '/dashboard'; @@ -27,6 +29,9 @@ class AppRoutes { switch (settings.name) { case login: return MaterialPageRoute(builder: (_) => const LoginScreen()); + + case mPin: + return MaterialPageRoute(builder: (_) => const MPinScreen()); case register: // Placeholder - create the RegisterScreen class and uncomment diff --git a/lib/config/themes.dart b/lib/config/themes.dart index 823237c..3a34fa8 100644 --- a/lib/config/themes.dart +++ b/lib/config/themes.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; class AppThemes { // Private constructor to prevent instantiation AppThemes._(); - + // Light theme colors static const Color _primaryColorLight = Color(0xFF1E88E5); // Blue 600 static const Color _secondaryColorLight = Color(0xFF26A69A); // Teal 400 @@ -22,51 +22,61 @@ 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', ), ); @@ -86,6 +96,7 @@ class AppThemes { // Light theme static final ThemeData lightTheme = ThemeData( useMaterial3: true, + fontFamily: 'Rubik', colorScheme: const ColorScheme.light( primary: _primaryColorLight, secondary: _secondaryColorLight, @@ -167,6 +178,7 @@ class AppThemes { // Dark theme static final ThemeData darkTheme = ThemeData( + fontFamily: 'Rubik', useMaterial3: true, colorScheme: const ColorScheme.dark( primary: _primaryColorDark, diff --git a/lib/features/auth/screens/login_screen.dart b/lib/features/auth/screens/login_screen.dart index a26a05d..6c44a97 100644 --- a/lib/features/auth/screens/login_screen.dart +++ b/lib/features/auth/screens/login_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:kmobile/features/auth/screens/mpin_screen.dart'; import '../controllers/auth_cubit.dart'; import '../controllers/auth_state.dart'; import '../../dashboard/screens/dashboard_screen.dart'; @@ -13,30 +14,34 @@ class LoginScreen extends StatefulWidget { class LoginScreenState extends State { final _formKey = GlobalKey(); - final _usernameController = TextEditingController(); + final _customerNumberController = TextEditingController(); final _passwordController = TextEditingController(); bool _obscurePassword = true; @override void dispose() { - _usernameController.dispose(); + _customerNumberController.dispose(); _passwordController.dispose(); super.dispose(); } void _submitForm() { - if (_formKey.currentState!.validate()) { - context.read().login( - _usernameController.text.trim(), - _passwordController.text, - ); - } + // if (_formKey.currentState!.validate()) { + // context.read().login( + // _customerNumberController.text.trim(), + // _passwordController.text, + // ); + // } + + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (context) => const MPinScreen()), + ); } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Login')), + // appBar: AppBar(title: const Text('Login')), body: BlocConsumer( listener: (context, state) { if (state is Authenticated) { @@ -56,19 +61,35 @@ class LoginScreenState extends State { key: _formKey, child: Column( mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, + // crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Bank logo or app branding - const FlutterLogo(size: 80), + const FlutterLogo(size: 100), + const SizedBox(height: 16), + // Title + const Text( + 'KCCB', + style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.blue), + ), const SizedBox(height: 48), TextFormField( - controller: _usernameController, + controller: _customerNumberController, decoration: const InputDecoration( - labelText: 'Username', - prefixIcon: Icon(Icons.person), + labelText: 'Customer Number', + // prefixIcon: Icon(Icons.person), border: OutlineInputBorder(), + isDense: true, + filled: true, + fillColor: Colors.white, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.black, width: 2), + ), ), + keyboardType: TextInputType.number, textInputAction: TextInputAction.next, validator: (value) { if (value == null || value.isEmpty) { @@ -83,8 +104,17 @@ class LoginScreenState extends State { 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 @@ -107,43 +137,72 @@ class LoginScreenState extends State { }, ), - 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), - 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'), + SizedBox( + width: 250, + child: ElevatedButton( + onPressed: state is AuthLoading ? null : _submitForm, + style: ElevatedButton.styleFrom( + shape: const StadiumBorder(), + padding: const EdgeInsets.symmetric(vertical: 16), + backgroundColor: Colors.white, + foregroundColor: Colors.blueAccent, + side: const BorderSide(color: Colors.black, width: 1), + elevation: 0 ), - ], + child: state is AuthLoading + ? const CircularProgressIndicator() + : const Text('Login', style: TextStyle(fontSize: 16),), + ), ), + + const SizedBox(height: 15), + + // 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'), + ), + ), + ], ), ), diff --git a/lib/features/auth/screens/mpin_screen.dart b/lib/features/auth/screens/mpin_screen.dart new file mode 100644 index 0000000..ed68e00 --- /dev/null +++ b/lib/features/auth/screens/mpin_screen.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; + +class MPinScreen extends StatefulWidget { + const MPinScreen({super.key}); + + @override + MPinScreenState createState() => MPinScreenState(); +} + +class MPinScreenState extends State { + List 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> keys = [ + ['1', '2', '3'], + ['4', '5', '6'], + ['7', '8', '9'], + ['', '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.isNotEmpty) { + addDigit(key); + } + }, + child: Container( + width: 70, + height: 70, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.grey[200], + ), + alignment: Alignment.center, + child: Text( + key == '<' ? '⌫' : key, + style: const TextStyle(fontSize: 24), + ), + ), + ), + ); + }).toList(), + ); + }).toList(), + ); + } + + @override + Widget build(BuildContext context) { + 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: () {}, child: const Text("Try another way")), + TextButton(onPressed: () {}, child: const Text("Register?")), + ], + ), + buildNumberPad(), + const Spacer(), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index e03e9f0..cdf8472 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -292,10 +292,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 url: "https://pub.dev" source: hosted - version: "2.2.16" + version: "2.2.17" path_provider_foundation: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4dd6a87..1515047 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -81,11 +81,12 @@ 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: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf + fonts: + - family: Rubik + fonts: + - asset: assets/fonts/Rubik-Regular.ttf + - asset: assets/fonts/Rubik-Bold.ttf + weight: 700 # style: italic # - family: Trajan Pro # fonts: